<script setup>
import { 
    computed, 
    defineAsyncComponent, 
    nextTick, 
    onBeforeUnmount, 
    onMounted, 
    ref, 
    shallowRef, 
    toRefs ,
    useSlots, 
    watch, 
    watchEffect,
} from "vue";
import { useConfig } from "../useConfig";
import { 
    XMLNS, 
    adaptColorToBackground,
    applyDataLabel,
    buildDisplayedTimeLabels,
    calculateNiceScale,
    calculateNiceScaleWithExactExtremes, 
    convertColorToHex, 
    convertCustomPalette, 
    createCsvContent,
    createTSpansFromLineBreaksOnX,
    createTSpansFromLineBreaksOnY,
    createUid,
    dataLabel, 
    downloadCsv,
    error, 
    forceValidValue, 
    functionReturnsString, 
    getMissingDatasetAttributes, 
    hasDeepProperty, 
    isFunction, 
    lightenHexColor, 
    objectIsEmpty, 
    palette, 
    sumSeries, 
    themePalettes, 
    treeShake, 
} from "../lib";
import { throttle } from "../canvas-lib";
import { usePrinter } from "../usePrinter";
import { useLoading } from "../useLoading.js";
import { useDateTime } from "../useDateTime.js";
import { useSvgExport } from "../useSvgExport.js";
import { useNestedProp } from "../useNestedProp";
import { useResponsive } from "../useResponsive";
import { useTimeLabels } from "../useTimeLabels";
import { useThemeCheck } from "../useThemeCheck.js";
import { useUserOptionState } from "../useUserOptionState";
import { useChartAccessibility } from "../useChartAccessibility";
import { useTimeLabelCollision } from '../useTimeLabelCollider.js';
import img from "../img";
import Title from "../atoms/Title.vue"; // Must be ready in responsive mode
import Shape from "../atoms/Shape.vue";
import themes from "../themes/vue_ui_stackbar.json";
import locales from '../locales/locales.json';
import Legend from "../atoms/Legend.vue"; // Must be ready in responsive mode
import BaseScanner from "../atoms/BaseScanner.vue";
import SlicerPreview from "../atoms/SlicerPreview.vue";

const Tooltip = defineAsyncComponent(() => import('../atoms/Tooltip.vue'));
const BaseIcon = defineAsyncComponent(() => import('../atoms/BaseIcon.vue'));
const Accordion = defineAsyncComponent(() => import('./vue-ui-accordion.vue'));
const DataTable = defineAsyncComponent(() => import('../atoms/DataTable.vue'));
const PenAndPaper = defineAsyncComponent(() => import('../atoms/PenAndPaper.vue'));
const UserOptions = defineAsyncComponent(() => import('../atoms/UserOptions.vue'));
const PackageVersion = defineAsyncComponent(() => import('../atoms/PackageVersion.vue'));
const BaseDraggableDialog = defineAsyncComponent(() => import('../atoms/BaseDraggableDialog.vue'));

const { vue_ui_stackbar: DEFAULT_CONFIG } = useConfig();
const { isThemeValid, warnInvalidTheme } = useThemeCheck();
const slots = useSlots();

const props = defineProps({
    config: {
        type: Object,
        default() {
            return {}
        }
    },
    dataset: {
        type: Array,
        default() {
            return []
        }
    },
    selectedXIndex: {
        type: Number,
        default: undefined
    }
});

const emit = defineEmits(['selectDatapoint', 'selectLegend', 'selectTimeLabel', 'selectX']);

const isDataset = computed({
    get() {
        return !!props.dataset && props.dataset.length
    },
    set(bool) {
        return bool
    }
});

const stackbarChart = ref(null);
const uid = ref(createUid());
const isTooltip = ref(false);
const dataTooltipSlot = ref(null);
const tooltipContent = ref('');
const segregated = ref([]);
const step = ref(0);
const chartTitle = ref(null);
const chartLegend = ref(null);
const chartSlicer = ref(null);
const source = ref(null);
const isFullscreen = ref(false);
const trapIndex = ref(null);
const isLoaded = ref(false);
const titleStep = ref(0);
const tableStep = ref(0);
const legendStep = ref(0);
const readyTeleport = ref(false);
const tableUnit = ref(null);
const userOptionsRef = ref(null);

const xAxisLabel = ref(null);
const yAxisLabel = ref(null);
const scaleLabels = ref(null);
const timeLabelsEls = ref(null);
const sumTop = ref(null);
const sumRight = ref(null);

const selectedMinimapIndex = ref(null);

onMounted(() => {
    readyTeleport.value = true;
    prepareChart();
})

const FINAL_CONFIG = ref(prepareConfig());

const { loading, FINAL_DATASET, manualLoading } = useLoading({
    ...toRefs(props),
    FINAL_CONFIG,
    prepareConfig,
    callback: () => {
        Promise.resolve().then(async () => {
            await setupSlicer();
        })
    },
    skeletonDataset: [
        {
            name: '',
            series: [2, 3, 5, 8, 13, 21],
            color: '#BABABA'
        },
        {
            name: '',
            series: [1, 2, 3, 5, 8, 13],
            color: '#CACACA'
        },
    ],
    skeletonConfig: treeShake({
        defaultConfig: FINAL_CONFIG.value,
        userConfig: {
            userOptions: { show: false },
            useCssAnimation: false,
            table: { show: false },
            tooltip: { show: false },
            style: {
                chart: {
                    backgroundColor: '#99999930',
                    bars: {
                        totalValues: { show: false },
                        dataLabels: { show: false },
                    },
                    grid: {
                        frame: {
                            stroke: '#6A6A6A'
                        },
                        scale: {
                            scaleMin: 0,
                            scaleMax: 40
                        },
                        x: { 
                            axisColor: '#6A6A6A',
                            linesColor: '#6A6A6A',
                            axisName: { show: false },
                            timeLabels: { show: false }
                        },
                        y: {
                            axisColor: '#6A6A6A',
                            linesColor: '#6A6A6A',
                            axisName: { show: false },
                            axisLabels: { show: false }
                        }
                    },
                    legend: {
                        backgroundColor: 'transparent'
                    },
                    padding: {
                        left: 24,
                        right: 24,
                        bottom: 12
                    },
                    zoom: { 
                        show: false,
                        startIndex: null,
                        endIndex: null
                    }
                }
            }
        }
    })
});

const { userOptionsVisible, setUserOptionsVisibility, keepUserOptionState } = useUserOptionState({ config: FINAL_CONFIG.value });
const { svgRef } = useChartAccessibility({ config: FINAL_CONFIG.value.style.chart.title });

function onChartEnter() {
    setUserOptionsVisibility(true);
}

function onChartLeave() {
    setUserOptionsVisibility(false);
    emit('selectX', { seriesIndex: null, datapoint: null });
    trapIndex.value = null;
}

function prepareConfig() {
    const mergedConfig = useNestedProp({
        userConfig: props.config,
        defaultConfig: DEFAULT_CONFIG,
    });
    let finalConfig = {};

    const theme = mergedConfig.theme;

    if (theme) {
        if (!isThemeValid.value(mergedConfig)) {
            warnInvalidTheme(mergedConfig);
            finalConfig = mergedConfig;
        } else {
            const fused = useNestedProp({
                userConfig: themes[theme] || props.config,
                defaultConfig: mergedConfig
            });
    
            finalConfig = {
                ...useNestedProp({
                    userConfig: props.config,
                    defaultConfig: fused
                }),
                customPalette: mergedConfig.customPalette.length ? mergedConfig.customPalette : themePalettes[theme] || palette
            }
        }
    } else {
        finalConfig = mergedConfig;
    }

    // ------------------------------ OVERRIDES -----------------------------------

    if (props.config && hasDeepProperty(props.config, 'style.chart.grid.scale.scaleMin')) {
        finalConfig.style.chart.grid.scale.scaleMin = props.config.style.chart.grid.scale.scaleMin;
    } else {
        finalConfig.style.chart.grid.scale.scaleMin = null;
    }

    if (props.config && hasDeepProperty(props.config, 'style.chart.grid.scale.scaleMax')) {
        finalConfig.style.chart.grid.scale.scaleMax = props.config.style.chart.grid.scale.scaleMax;
    } else {
        finalConfig.style.chart.grid.scale.scaleMax = null;
    }

    if (props.config && hasDeepProperty(props.config, 'style.chart.zoom.startIndex')) {
        finalConfig.style.chart.zoom.startIndex = props.config.style.chart.zoom.startIndex;
    } else {
        finalConfig.style.chart.zoom.startIndex = null;
    }

    if (props.config && hasDeepProperty(props.config, 'style.chart.zoom.endIndex')) {
        finalConfig.style.chart.zoom.endIndex = props.config.style.chart.zoom.endIndex;
    } else {
        finalConfig.style.chart.zoom.endIndex = null;
    }

    if (props.config && hasDeepProperty(props.config, 'style.chart.bars.dataLabels.hideUnderValue')) {
        finalConfig.style.chart.bars.dataLabels.hideUnderValue = props.config.style.chart.bars.dataLabels.hideUnderValue;
    } else {
        finalConfig.style.chart.bars.dataLabels.hideUnderValue = null;
    }

    if (props.config && hasDeepProperty(props.config, 'style.chart.bars.dataLabels.hideUnderPercentage')) {
        finalConfig.style.chart.bars.dataLabels.hideUnderPercentage = props.config.style.chart.bars.dataLabels.hideUnderPercentage;
    } else {
        finalConfig.style.chart.bars.dataLabels.hideUnderPercentage = null;
    }

    // ----------------------------------------------------------------------------

    return finalConfig;
}

const canHideSmallValues = computed(() => {
    return FINAL_CONFIG.value.style.chart.bars.dataLabels.hideUnderValue !== null
});

const canHideSmallPercentages = computed(() => {
    return FINAL_CONFIG.value.style.chart.bars.dataLabels.hideUnderPercentage !== null
})

watch(() => props.config, (_newCfg) => {
    if (!loading.value) {
        FINAL_CONFIG.value = prepareConfig();
    }
    userOptionsVisible.value = !FINAL_CONFIG.value.userOptions.showOnChartHover;
    prepareChart();
    titleStep.value += 1;
    tableStep.value += 1;
    legendStep.value += 1;
    
    // Reset mutable config
    mutableConfig.value.dataLabels.show = FINAL_CONFIG.value.style.chart.bars.dataLabels.show;
    mutableConfig.value.showTable = FINAL_CONFIG.value.table.show;
    mutableConfig.value.showTooltip = FINAL_CONFIG.value.style.chart.tooltip.show;

    normalizeSlicerWindow();
}, { deep: true });

watch(() => props.dataset, (_) => {
    if (Array.isArray(_) && _.length > 0) {
        manualLoading.value = false;
    }
    refreshSlicer();
}, { deep: true })

const mutableConfig = ref({
    dataLabels: {
        show: FINAL_CONFIG.value.style.chart.bars.dataLabels.show,
    },
    showTable: FINAL_CONFIG.value.table.show,
    showTooltip: FINAL_CONFIG.value.style.chart.tooltip.show
});

// v3 - Essential to make shifting between loading config and final config work
watch(FINAL_CONFIG, () => {
    mutableConfig.value = {
        dataLabels: {
            show: FINAL_CONFIG.value.style.chart.bars.dataLabels.show,
        },
        showTable: FINAL_CONFIG.value.table.show,
        showTooltip: FINAL_CONFIG.value.style.chart.tooltip.show
    }
}, { immediate: true });

const { isPrinting, isImaging, generatePdf, generateImage } = usePrinter({
    elementId: `stackbar_${uid.value}`,
    fileName: FINAL_CONFIG.value.style.chart.title.text || 'vue-ui-stackbar',
    options: FINAL_CONFIG.value.userOptions.print
});

const defaultSizes = ref({
    width: FINAL_CONFIG.value.style.chart.width,
    height: FINAL_CONFIG.value.style.chart.height,
    paddingRatio: {
        top: FINAL_CONFIG.value.style.chart.padding.top / FINAL_CONFIG.value.style.chart.height,
        right: FINAL_CONFIG.value.style.chart.padding.right / FINAL_CONFIG.value.style.chart.width,
        bottom: FINAL_CONFIG.value.style.chart.padding.bottom / FINAL_CONFIG.value.style.chart.height,
        left: FINAL_CONFIG.value.style.chart.padding.left / FINAL_CONFIG.value.style.chart.width,
    }
});

const customPalette = computed(() => {
    return convertCustomPalette(FINAL_CONFIG.value.customPalette);
});

const resizeObserver = shallowRef(null);
const observedEl = shallowRef(null);
const to = ref(null)
onMounted(() => {
    prepareChart();
});

const debug = computed(() => !!FINAL_CONFIG.value.debug);

function prepareChart() {
    if(objectIsEmpty(props.dataset)) {
        error({
            componentName: 'VueUiStackbar',
            type: 'dataset',
            debug: debug.value
        });
        manualLoading.value = true;
    } else {
        props.dataset.forEach((datasetObject, index) => {
            getMissingDatasetAttributes({
                datasetObject,
                requiredAttributes: ['name', 'series']
            }).forEach(attr => {
                isDataset.value = false;
                error({
                    componentName: 'VueUiStackbar',
                    type: 'datasetSerieAttribute',
                    property: attr,
                    index,
                    debug: debug.value
                });
                manualLoading.value = true;
            })
        })
    }

    // v3
    if (!objectIsEmpty(props.dataset)) {
        manualLoading.value = FINAL_CONFIG.value.loading;
    }

    setTimeout(() => {
        isLoaded.value = true;
    }, 10)

    if (FINAL_CONFIG.value.responsive) {
        const handleResize = throttle(() => {
            isLoaded.value = false; // unset rect transitions as it looks janky during resizing process
            const { width, height } = useResponsive({
                chart: stackbarChart.value,
                title: FINAL_CONFIG.value.style.chart.title.text ? chartTitle.value : null,
                legend: FINAL_CONFIG.value.style.chart.legend.show ? chartLegend.value : null,
                slicer: FINAL_CONFIG.value.style.chart.zoom.show && maxSeries.value > 6 ? chartSlicer.value.$el : null,
                source: source.value
            });

            requestAnimationFrame(() => {
                defaultSizes.value.width = width;
                defaultSizes.value.height = height - 12;
                clearTimeout(to.value);
                to.value = setTimeout(() => {
                    isLoaded.value = true;
                },10)
            });
        });

        if (resizeObserver.value) {
            if (observedEl.value) {
                resizeObserver.value.unobserve(observedEl.value);
            }
            resizeObserver.value.disconnect();
        }

        resizeObserver.value = new ResizeObserver(handleResize);
        observedEl.value = stackbarChart.value.parentNode;
        resizeObserver.value.observe(observedEl.value);
    }
    setupSlicer();
}

onBeforeUnmount(() => {
    if (resizeObserver.value) {
        if (observedEl.value) {
            resizeObserver.value.unobserve(observedEl.value);
        }
        resizeObserver.value.disconnect();
    }
});

function getOffsetX() {
    let base = 0;
    if (FINAL_CONFIG.value.orientation === 'vertical') {
        if (scaleLabels.value) {
            const texts = Array.from(scaleLabels.value.querySelectorAll('text'))
            base = texts.reduce((max, t) => {
                const w = t.getComputedTextLength()
                return w > max ? w : max
            }, 0)
        }
    }
    if (FINAL_CONFIG.value.orientation === 'horizontal') {
        if (timeLabelsEls.value) {
            const texts = Array.from(timeLabelsEls.value.querySelectorAll('text'))
            base = texts.reduce((max, t) => {
                const w = t.getComputedTextLength()
                return w > max ? w : max
            }, 0)
        }
    }

    const yAxisLabelW = yAxisLabel.value
        ? yAxisLabel.value.getBoundingClientRect().width
        : 0

    return base + yAxisLabelW + (yAxisLabelW ? 24 : 0);
}

const labelsXHeight = ref(0);
const offsetRight = ref(0);

const updateHeight = throttle((h) => {
    labelsXHeight.value = h;
}, 100);

watchEffect((onInvalidate) => {
    const el = FINAL_CONFIG.value.orientation === 'vertical' ? timeLabelsEls.value : scaleLabels.value;
    if (!el) return

    const observer = new ResizeObserver(entries => {
        updateHeight(entries[0].contentRect.height)
    })
    observer.observe(el)
    onInvalidate(() => observer.disconnect())
});

const updateOffsetRight = throttle((w) => {
    offsetRight.value = w + FINAL_CONFIG.value.style.chart.bars.totalValues.fontSize
}, 100);


function computeRightOverhang() {
    if (FINAL_CONFIG.value.orientation !== 'horizontal') return 0;

    const group = sumRight.value;
    if (!group) return 0;

    const texts = Array.from(group.querySelectorAll('text'));
    if (!texts.length) return 0;

    let maxRight = -Infinity;
    for (const t of texts) {
        try {
        const box = t.getBBox();
        const right = box.x + box.width;
        if (right > maxRight) maxRight = right;
        } catch (_) {
            //
        }
    }

    const overhang = Math.max(0, maxRight - (drawingArea.value?.right ?? 0));
    return overhang;
}

watchEffect((onInvalidate) => {
    if (FINAL_CONFIG.value.orientation !== 'horizontal') return;

    const el = sumRight.value;
    if (!el) return;

    const onChange = () => {
        const overhang = computeRightOverhang();
        updateOffsetRight(overhang);
    };

    onChange();
    const resizeObserver = new ResizeObserver(onChange);
    resizeObserver.observe(el);
    const mutationObserver = new MutationObserver(onChange);
    mutationObserver.observe(el, { childList: true, subtree: true, characterData: true });

    onInvalidate(() => {
        resizeObserver.disconnect();
        mutationObserver.disconnect();
    });
});


onBeforeUnmount(() => {
    labelsXHeight.value = 0;
    offsetRight.value = 0;
});

const offsetY = computed(() => {
    let h = 0;
        if (xAxisLabel.value) {
            h = xAxisLabel.value.getBBox().height;
        }
        let tlH = 0;
        if (FINAL_CONFIG.value.orientation === 'vertical') {
            if (timeLabelsEls.value) {
                tlH = labelsXHeight.value;
            }
        }
        if (FINAL_CONFIG.value.orientation === 'horizontal') {
            if (scaleLabels.value) {
                tlH = labelsXHeight.value;
            }
        }
        return h + tlH;
});

const drawingArea = computed(() => {
    const { height: H, width: W } = defaultSizes.value;
    const { right: PR } = defaultSizes.value.paddingRatio;

    let topOffset = FINAL_CONFIG.value.style.chart.bars.totalValues.show && props.dataset && props.dataset.length > 1 ? FINAL_CONFIG.value.style.chart.bars.totalValues.fontSize * 1.3 : 0;
    let offsetX = 0;

    if (FINAL_CONFIG.value.style.chart.grid.y.axisLabels.show) {
        offsetX = getOffsetX()
    }

    const top = FINAL_CONFIG.value.style.chart.padding.top + topOffset
    const right = W - (W * PR) - offsetRight.value;
    const bottom = H - FINAL_CONFIG.value.style.chart.padding.bottom - offsetY.value - topOffset;
    const left = FINAL_CONFIG.value.style.chart.padding.left + offsetX;

    const width = W - left - (W * PR) - offsetRight.value;
    const height = H - top - FINAL_CONFIG.value.style.chart.padding.bottom - offsetY.value - topOffset;

    return {
        chartHeight: H,
        chartWidth: W,
        top,
        right,
        bottom,
        left,
        width : Math.max(0, width),
        height: Math.max(0, height),
    }
});

const precogRect = computed(() => {
    const { left, top, width: totalWidth, height: totalHeight } = drawingArea.value;
    const windowStart = slicer.value.start;
    const windowEnd = slicer.value.end;
    const windowLen = Math.max(1, windowEnd - windowStart);

    const relStart = Math.max(0, Math.min(windowLen, (slicerPrecog.value.start ?? windowStart) - windowStart));
    const relEnd = Math.max(0, Math.min(windowLen, (slicerPrecog.value.end   ?? windowEnd)   - windowStart));
    const span = Math.max(0, relEnd - relStart);

    const commonCfg = {
        fill: FINAL_CONFIG.value.style.chart.zoom.preview.fill,
        stroke: FINAL_CONFIG.value.style.chart.zoom.preview.stroke,
        ['stroke-width']: FINAL_CONFIG.value.style.chart.zoom.preview.strokeWidth,
        ['stroke-dasharray']: FINAL_CONFIG.value.style.chart.zoom.preview.strokeDasharray,
        ['stroke-linecap']: 'round',
        ['stroke-linejoin']: 'round',
        style: {
            pointerEvents: 'none',
            transition: 'none !important',
            animation: 'none !important'
        }
    };

    if (FINAL_CONFIG.value.orientation === 'horizontal') {
        const unit = totalHeight / windowLen;
        return {
            x: left,
            y: top + relStart * unit,
            width: totalWidth,
            height: span * unit,
            ...commonCfg
        };
    } else {
        const unit = totalWidth / windowLen;
        return {
            x: left + relStart * unit,
            y: top,
            width: span * unit,
            height: totalHeight,
            ...commonCfg
        };
    }
});

const unmutableDataset = computed(() => {
    return FINAL_DATASET.value.map((ds, i) => {
        const color = convertColorToHex(ds.color) || customPalette.value[i] || palette[i] || palette[i % palette.length];
        return {
            ...ds,
            // In distributed mode, all values are converted to positive
            series: JSON.parse(JSON.stringify(ds.series)).map(v => {
                return FINAL_CONFIG.value.style.chart.bars.distributed ? Math.abs(v) : v
            }),
            // Store signs to manage display of neg values in distributed mode
            signedSeries: ds.series.map(v => v >= 0 ? 1 : -1),
            absoluteIndex: i,
            id: createUid(),
            color
        }
    });
});

const maxSeries = computed(() => {
    return Math.max(...unmutableDataset.value.filter(ds => !segregated.value.includes(ds.id)).map(ds => ds.series.length))
});


function selectMinimapIndex(i) {
    selectedMinimapIndex.value = i;
}

const slicer = ref({
    start: 0,
    end: Math.max(...FINAL_DATASET.value.map(ds => ds.series.length))
});

const slicerPrecog = ref({ start: 0, end: Math.max(...FINAL_DATASET.value.map(ds => ds.series.length)) });

const isPrecog = computed(() => {
    return FINAL_CONFIG.value.style.chart.zoom.preview.enable && (slicerPrecog.value.start !== slicer.value.start || slicerPrecog.value.end !== slicer.value.end);
});

function setPrecog(side, val) {
    slicerPrecog.value[side] = val;
}

function normalizeSlicerWindow() {
    const maxLen = maxSeries.value;
    let s = Math.max(0, Math.min(slicer.value.start ?? 0, maxLen - 1))
    let e = Math.max(s + 1, Math.min(slicer.value.end ?? maxLen, maxLen))

    if (!Number.isFinite(s) || !Number.isFinite(e) || e <= s) { s = 0; e = maxLen }

    slicer.value.start = s;
    slicer.value.end = e;
    slicerPrecog.value.start = s
    slicerPrecog.value.end = e

    if(chartSlicer.value) {
        chartSlicer.value.setStartValue(s);
        chartSlicer.value.setEndValue(e)
    }
}

const refreshRAF = ref(null);
function nextPaint() {
    return new Promise(r => requestAnimationFrame(() =>
        requestAnimationFrame(() => r())
    ));
}

onBeforeUnmount(() => {
    if (refreshRAF.value) cancelAnimationFrame(refreshRAF.value);
});

async function refreshSlicer() {
    setupSlicer();

    // Now this is basically sweeping shit under the rug so it works:
    await nextTick();
    if (refreshRAF.value) cancelAnimationFrame(refreshRAF.value);
    refreshRAF.value = requestAnimationFrame(async () => {
        await nextPaint();
        setupSlicer();
    });
}

const barSlot = computed(() => {
    let bs;
    if (FINAL_CONFIG.value.orientation === 'vertical') {
        bs = drawingArea.value.width / (slicer.value.end - slicer.value.start);
    } else {
        bs = drawingArea.value.height / (slicer.value.end - slicer.value.start);
    }
    return bs <= 0 ? 0 : bs;
});

const datasetTotals = computed(() => {
    return sumSeries(unmutableDataset.value.filter(ds => !segregated.value.includes(ds.id))).slice(slicer.value.start, slicer.value.end);
});

const datasetTotalsMinimap = computed(() => {
    if (!FINAL_CONFIG.value.style.chart.zoom.minimap.show) return [];
    return sumSeries(unmutableDataset.value.map(ds => {
        return {
            ...ds,
            series: ds.series.map(d => d ?? 0)
        }
    }).filter(ds => !segregated.value.includes(ds.id)))
});

const allMinimaps = computed(() => {
    if (!FINAL_CONFIG.value.style.chart.zoom.minimap.show) return [];
    return [{
        name: '',
        series: datasetTotalsMinimap.value,
        color: '#000000',
        isVisible: true
    }]
})

const displayTotals = computed(() => {
    return sumSeries(unmutableDataset.value.filter(ds => !segregated.value.includes(ds.id)).map(s => {
        return {
            ...s,
            series: s.series.map((dp,i) => {
                return s.signedSeries[i] === -1 ? (dp >= 0 ? -dp : dp) : dp
            })
        }
    })).slice(slicer.value.start, slicer.value.end);
})

const datasetSignedTotals = computed(() => {
    const src = unmutableDataset.value.filter(ds => !segregated.value.includes(ds.id))
    return { 
        positive: sumSeries(src.map(s => {
            return {
                ...s,
                series: s.series.slice(slicer.value.start, slicer.value.end).map(v => v >= 0 ? v : 0)
            }
        })),
        negative: sumSeries(src.map(s => {
            return {
                ...s,
                series: s.series.slice(slicer.value.start, slicer.value.end).map(v => v < 0 ? v : 0)
            }
        }))
    }
});

const yLabels = computed(() => {
    const MAX = (FINAL_CONFIG.value.style.chart.grid.scale.scaleMax !== null && !FINAL_CONFIG.value.style.chart.bars.distributed)  ? FINAL_CONFIG.value.style.chart.grid.scale.scaleMax : Math.max(...datasetSignedTotals.value.positive);
    const workingMin = Math.min(...datasetSignedTotals.value.negative);
    const MIN = (FINAL_CONFIG.value.style.chart.grid.scale.scaleMin !== null && !FINAL_CONFIG.value.style.chart.bars.distributed) ? FINAL_CONFIG.value.style.chart.grid.scale.scaleMin : [-Infinity, Infinity, NaN, undefined, null].includes(workingMin) ? 0 : workingMin;

    const scale = (!FINAL_CONFIG.value.style.chart.bars.distributed && (FINAL_CONFIG.value.style.chart.grid.scale.scaleMax !== null || FINAL_CONFIG.value.style.chart.grid.scale.scaleMin !== null)) ? calculateNiceScaleWithExactExtremes((MIN > 0 ? 0 : MIN), MAX < 0 ? 0 : MAX, FINAL_CONFIG.value.style.chart.grid.scale.ticks) : calculateNiceScale((MIN > 0 ? 0 : MIN), MAX < 0 ? 0 : MAX, FINAL_CONFIG.value.style.chart.grid.scale.ticks);
    return scale.ticks.map(t => {
        return {
            zero: drawingArea.value.bottom - (drawingArea.value.height * ((Math.abs(scale.min)) / (scale.max + Math.abs(scale.min)))),
            y: drawingArea.value.bottom - (drawingArea.value.height * ((t + Math.abs(scale.min)) / ((scale.max) + Math.abs(scale.min)))),
            x: drawingArea.value.left - 8,
            horizontal_zero: drawingArea.value.left  + (drawingArea.value.width * ((Math.abs(scale.min)) / (scale.max + Math.abs(scale.min)))),
            horizontal_x: drawingArea.value.left + (drawingArea.value.width * ((t + Math.abs(scale.min)) / ((scale.max) + Math.abs(scale.min)))),
            horizontal_y: drawingArea.value.bottom - 8, 
            value: t
        }
    });
});

const timeLabels = computed(() => {
    return useTimeLabels({
        values: FINAL_CONFIG.value.style.chart.grid.x.timeLabels.values,
        maxDatapoints: maxSeries.value,
        formatter: FINAL_CONFIG.value.style.chart.grid.x.timeLabels.datetimeFormatter,
        start: slicer.value.start,
        end: slicer.value.end
    })
});

const allTimeLabels = computed(() => {
    return useTimeLabels({
        values: FINAL_CONFIG.value.style.chart.grid.x.timeLabels.values,
        maxDatapoints: maxSeries.value,
        formatter: FINAL_CONFIG.value.style.chart.grid.x.timeLabels.datetimeFormatter,
        start: 0,
        end: maxSeries.value
    })
});

const modulo = computed(() => {
    const m = FINAL_CONFIG.value.style.chart.grid.x.timeLabels.modulo;
    if (!timeLabels.value.length) return m;
    return Math.min(m, [...new Set(timeLabels.value.map(t => t.text))].length);
});

const displayedTimeLabels = computed(() => {
    const cfg = FINAL_CONFIG.value.style.chart.grid.x.timeLabels;
    const vis = timeLabels.value || [];
    const all = allTimeLabels.value || [];
    const start = slicer.value.start ?? 0;
    const sel = trapIndex.value;
    const maxS = maxSeries.value;
    const visTexts = vis.map(l => l?.text ?? '');
    const allTexts = all.map(l => l?.text ?? '');

    return buildDisplayedTimeLabels(
        !!cfg.showOnlyFirstAndLast,
        !!cfg.showOnlyAtModulo,
        Math.max(1, modulo.value || 1),
        visTexts,
        allTexts,
        start,
        sel,
        maxS
    );
});

const preciseTimeFormatter = computed(() => {
    const xl = FINAL_CONFIG.value.style.chart.grid.x.timeLabels.datetimeFormatter

    const dt = useDateTime({
        useUTC: xl.useUTC,
        locale: locales[xl.locale] || { months:[], shortMonths:[], days:[], shortDays:[] },
        januaryAsYear: xl.januaryAsYear
    });

    return (absIndex, fmt) => {
        const values = FINAL_CONFIG.value.style.chart.grid.x.timeLabels.values
        const ts = values?.[absIndex]
        if (ts == null) return ''
        const res = dt.formatDate(new Date(ts), fmt)
        return dt.formatDate(new Date(ts), fmt)
    }
});

const preciseAllTimeLabels = computed(() => {
    const values = FINAL_CONFIG.value.style.chart.grid.x.timeLabels.values || []
    return values.map((_, i) => ({
        text: preciseTimeFormatter.value(i, FINAL_CONFIG.value.style.chart.zoom.timeFormat),
        absoluteIndex: i
    }));
});

const preciseAllTimeLabelsTooltip = computed(() => {
    const values = FINAL_CONFIG.value.style.chart.grid.x.timeLabels.values || []
    return values.map((_, i) => ({
        text: preciseTimeFormatter.value(i, FINAL_CONFIG.value.style.chart.tooltip.timeFormat),
        absoluteIndex: i
    }));
});

const formattedDataset = computed(() => {
    if (!isDataset.value && !loading.value) return [];

    let cumulativeY = Array(maxSeries.value).fill(0);
    let cumulativeX = Array(maxSeries.value).fill(0);
    let cumulativeNegY = Array(maxSeries.value).fill(0);
    let cumulativeNegX = Array(maxSeries.value).fill(0);
    
    const premax = Math.max(...datasetSignedTotals.value.positive) || 0;
    const workingMin = Math.min(...datasetSignedTotals.value.negative);
    const premin = [-Infinity, Infinity, NaN, undefined, null].includes(workingMin) ? 0 : workingMin;

    const scale = (!FINAL_CONFIG.value.style.chart.bars.distributed && (FINAL_CONFIG.value.style.chart.grid.scale.scaleMax !== null || FINAL_CONFIG.value.style.chart.grid.scale.scaleMin !== null)) ? calculateNiceScaleWithExactExtremes(FINAL_CONFIG.value.style.chart.grid.scale.scaleMin !== null ? FINAL_CONFIG.value.style.chart.grid.scale.scaleMin : (premin > 0 ? 0 : premin), FINAL_CONFIG.value.style.chart.grid.scale.scaleMax !== null ? FINAL_CONFIG.value.style.chart.grid.scale.scaleMax : premax < 0 ? 0 : premax, FINAL_CONFIG.value.style.chart.grid.scale.ticks) : calculateNiceScale(FINAL_CONFIG.value.style.chart.grid.scale.scaleMin !== null ? FINAL_CONFIG.value.style.chart.grid.scale.scaleMin : (premin > 0 ? 0 : premin), FINAL_CONFIG.value.style.chart.grid.scale.scaleMax !== null ? FINAL_CONFIG.value.style.chart.grid.scale.scaleMax : premax < 0 ? 0 : premax, FINAL_CONFIG.value.style.chart.grid.scale.ticks);
    const { min: MIN, max: MAX } = scale;
    
    const maxTotal = (MAX + (MIN >= 0 ? 0 : Math.abs(MIN))) || 1

    const totalHeight = drawingArea.value.height;
    const totalWidth = drawingArea.value.width;
    const ZERO_POSITION = yLabels.value[0] ? yLabels.value[0].zero : drawingArea.value.bottom;
    const HORIZONTAL_ZERO_POSITION = yLabels.value[0] ? yLabels.value[0].horizontal_zero : drawingArea.value.left;

    let posPrefixMini = Array(datasetTotalsMinimap.value.length).fill(0);
let negPrefixMini = Array(datasetTotalsMinimap.value.length).fill(0);

    return unmutableDataset.value
        .filter(ds => !segregated.value.includes(ds.id))
        .map(ds => {
            const fullSeries = ds.series.slice();

            const posOffsetMini = fullSeries.map((v, i) => posPrefixMini[i]);
            const negOffsetMini = fullSeries.map((v, i) => negPrefixMini[i]);

            fullSeries.forEach((v, i) => {
                if ((Number(v) || 0) >= 0) {
                    posPrefixMini[i] += (Number(v) || 0);
                } else {
                    negPrefixMini[i] += Math.abs(Number(v) || 0);
                }
            });

            const slicedSeries = ds.series.slice(slicer.value.start, slicer.value.end);
            const signedSliced = ds.signedSeries.slice(slicer.value.start, slicer.value.end);

            const x = slicedSeries.map((_dp, i) => {
                return drawingArea.value.left + (barSlot.value * i) + (barSlot.value * FINAL_CONFIG.value.style.chart.bars.gapRatio / 4);
            });

            const valsMini = (datasetTotalsMinimap.value || []).filter(Number.isFinite);
            const premaxMini = valsMini.length ? Math.max(...valsMini) : 0;
            const preminMini = valsMini.length ? Math.min(...valsMini) : 0;

            function makeMiniScale({ minimapH }) {
            const EPS = 1e-9;
            const hasPos = premaxMini > 0;
            const hasNeg = preminMini < 0;

            if (hasPos && hasNeg) {
                const M = Math.max(premaxMini, Math.abs(preminMini)) || EPS;
                return {
                    pxPerUnit: (minimapH / 2) / M,
                    zero: minimapH / 2
                };
            }

            if (hasPos) {
                const span = Math.max(EPS, premaxMini);
                return {
                    pxPerUnit: minimapH / span,
                    zero: minimapH
                };
            }

            const span = Math.max(EPS, Math.abs(preminMini));
                return {
                    pxPerUnit: minimapH / span,
                    zero: 0
                };
            }

            const xMinimap = ({ left, unitW }) => {
                const gap = unitW * (FINAL_CONFIG.value.style.chart.bars.gapRatio / 4);
                return fullSeries.map((_, i) => left + unitW * i + gap);
            };

            const horizontal_y = slicedSeries.map((_dp, i) => {
                return drawingArea.value.top + (barSlot.value * i) + (barSlot.value * FINAL_CONFIG.value.style.chart.bars.gapRatio / 4);
            })

            const y = slicedSeries.map((dp, i) => {
                const proportion = FINAL_CONFIG.value.style.chart.bars.distributed
                    ? (dp || 0) / datasetTotals.value[i]
                    : (dp || 0) / maxTotal;

                let currentY, height;
                if (dp > 0) {
                    height = totalHeight * proportion; 
                    currentY = ZERO_POSITION - height - cumulativeY[i];
                    cumulativeY[i] += height;
                } else {
                    height = totalHeight * proportion;
                    currentY = ZERO_POSITION + cumulativeNegY[i]
                    cumulativeNegY[i] += Math.abs(height)
                }
                return currentY;
            });

            const yMinimap = ({ minimapH }) => {
                if (FINAL_CONFIG.value.style.chart.bars.distributed) {
                    const EPS = 1e-9;
                    return fullSeries.map((raw, i) => {
                        const vAbs = Math.abs(Number(raw) || 0);
                        const total = Math.abs(datasetTotalsMinimap.value?.[i] || 0) || EPS;
                        const prevAbs = posOffsetMini[i];
                        const topProportion = (prevAbs + vAbs) / total;
                        return minimapH - topProportion * minimapH;
                    });
                }

                const { pxPerUnit, zero } = makeMiniScale({ minimapH });
                return fullSeries.map((raw, i) => {
                    const v = Number(raw) || 0;
                    return v >= 0
                    ? zero - (posOffsetMini[i] + v) * pxPerUnit
                    : zero +  negOffsetMini[i]       * pxPerUnit;
                });
            };

            const horizontal_x = slicedSeries.map((dp, i) => {
                const proportion = FINAL_CONFIG.value.style.chart.bars.distributed
                    ? (dp || 0) / datasetTotals.value[i]
                    : (dp || 0) / maxTotal;

                let currentX, hw;
                if (dp > 0) {
                    hw = totalWidth * proportion; 
                    currentX = HORIZONTAL_ZERO_POSITION + cumulativeX[i];
                    cumulativeX[i] += hw;
                } else {
                    hw = totalWidth * proportion;
                    currentX = HORIZONTAL_ZERO_POSITION - Math.abs(hw) - cumulativeNegX[i]
                    cumulativeNegX[i] += Math.abs(hw)
                }
                return currentX;
            });

            const height = slicedSeries.map((dp, i) => {
                const proportion = FINAL_CONFIG.value.style.chart.bars.distributed
                    ? (dp || 0) / datasetTotals.value[i]
                    : (dp || 0) / maxTotal;

                    if (dp > 0) {
                        return totalHeight * proportion
                    } else {
                        return totalHeight * Math.abs(proportion)
                    }
            });

            const heightMinimap = ({ minimapH }) => {
            if (FINAL_CONFIG.value.style.chart.bars.distributed) {
                const EPS = 1e-9;
                return fullSeries.map((raw, i) => {
                    const vAbs = Math.abs(Number(raw) || 0);
                    const total = Math.abs(datasetTotalsMinimap.value?.[i] || 0) || EPS;
                    const proportion = vAbs / total;
                    return proportion * minimapH;
                });
            }

            const { pxPerUnit } = makeMiniScale({ minimapH });
                return fullSeries.map(raw => Math.abs(Number(raw) || 0) * pxPerUnit);
            };

            const horizontal_width = slicedSeries.map((dp, i) => {
                const proportion = FINAL_CONFIG.value.style.chart.bars.distributed
                    ? (dp || 0) / datasetTotals.value[i]
                    : (dp || 0) / maxTotal;

                    if (dp > 0) {
                        return totalWidth * proportion
                    } else {
                        return totalWidth * Math.abs(proportion)
                    }
            });

            const absoluteTotal = slicedSeries.map(v => Math.abs(v)).reduce((a, b) => a + b, 0);

            return {
                ...ds,
                proportions: slicedSeries.map((dp, i) => {
                    if (FINAL_CONFIG.value.style.chart.bars.distributed) {
                        return (dp || 0) / datasetTotals.value[i];
                    } else {
                        return (dp || 0) / absoluteTotal;
                    }
                }),
                series: slicedSeries,
                signedSeries: signedSliced,
                x,
                y,
                height,
                horizontal_width,
                horizontal_y,
                horizontal_x,
                xMinimap,
                yMinimap,
                heightMinimap
            };
        });
});

const totalLabels = computed(() => {
    return displayTotals.value.map((t, i) => {
        return {
            value: t,
            sign: t >= 0 ? 1 : -1
        }
    });
});

function barDataLabel(val, datapoint, index, dpIndex, signed) {

    const appliedValue = signed === - 1 ? (val >= 0 ? -val : val) : val
    return applyDataLabel(
        FINAL_CONFIG.value.style.chart.bars.dataLabels.formatter,
        appliedValue,
        dataLabel({
            p: FINAL_CONFIG.value.style.chart.bars.dataLabels.prefix,
            v: appliedValue,
            s: FINAL_CONFIG.value.style.chart.bars.dataLabels.suffix,
            r: FINAL_CONFIG.value.style.chart.bars.dataLabels.rounding,
        }),
        { datapoint, seriesIndex: index, datapointIndex: dpIndex }
    )
}

function barDataLabelPercentage(val, datapoint, index, dpIndex) {
    return applyDataLabel(
        FINAL_CONFIG.value.style.chart.bars.dataLabels.formatter,
        val,
        dataLabel({
            v: isNaN(val) ? 0 : val,
            s: '%',
            r: FINAL_CONFIG.value.style.chart.bars.dataLabels.rounding,
        }),
        { datapoint, seriesIndex: index, datapointIndex: dpIndex }
    )
}

function selectDatapoint(index) {
    const datapoint = JSON.parse(JSON.stringify(formattedDataset.value)).map(fd => {
        return {
            name: fd.name,
            value: fd.series[index] === 0 ? 0 : fd.series[index] || null,
            proportion: fd.proportions[index] || null,
            color: fd.color,
            id: fd.id
        }
    });

    if (FINAL_CONFIG.value.events.datapointClick) {
        FINAL_CONFIG.value.events.datapointClick({ datapoint, seriesIndex: index + slicer.value.start })
    }

    emit('selectDatapoint', { datapoint, period: timeLabels.value[index] });
}

function getDatapoint(seriesIndex) {
    return JSON.parse(JSON.stringify(formattedDataset.value)).map(fd => {
        return {
            name: fd.name,
            absoluteIndex: fd.absoluteIndex,
            value: fd.series[seriesIndex] === 0 ? 0 : (fd.signedSeries[seriesIndex] === -1 ? (fd.series[seriesIndex] >= 0 ? -fd.series[seriesIndex] : fd.series[seriesIndex]) : fd.series[seriesIndex]) || null,
            proportion: fd.proportions[seriesIndex] || null,
            color: fd.color,
            id: fd.id,
            timeLabel: allTimeLabels.value[seriesIndex]
        }
    });
}

function onTrapLeave(i) {
    if (FINAL_CONFIG.value.events.datapointLeave) {
        const datapoint = getDatapoint(i);
        FINAL_CONFIG.value.events.datapointLeave({ datapoint, seriesIndex: i + slicer.value.start });
    }
    isTooltip.value = null;
    trapIndex.value = null;
}

const datapointsForSlicerCustomFormat = computed(() => {
    return Array(maxSeries.value).fill(0).map((_,i) => getDatapoint(i))
});

watch(() => props.selectedXIndex, (v) => {
    if ([null, undefined].includes(props.selectedXIndex)) {
        trapIndex.value = null;
        return;
    }

    const targetIndex = v - slicer.value.start;
    if (targetIndex < 0 || v >= slicer.value.end) {
        trapIndex.value = null;
    } else {
        trapIndex.value = targetIndex ?? null;
    }
}, { immediate: true })

function useTooltip(seriesIndex) {
    trapIndex.value = seriesIndex;
    isTooltip.value = true;

    const customFormat = FINAL_CONFIG.value.style.chart.tooltip.customFormat;
    const datapoint = getDatapoint(seriesIndex);

    selectX({ seriesIndex, datapoint });

    if (FINAL_CONFIG.value.events.datapointEnter) {
        FINAL_CONFIG.value.events.datapointEnter({ datapoint, seriesIndex: seriesIndex + slicer.value.start });
    }

    dataTooltipSlot.value = {
        datapoint,
        seriesIndex,
        config: FINAL_CONFIG.value,
        series: formattedDataset.value
    }

    const sum = datapoint.map(d => Math.abs(d.value)).reduce((a, b) => a + b, 0);
    const sumLabel = datapoint.map(d => forceValidValue(d.value)).reduce((a, b) => a + b, 0);

    if (isFunction(customFormat) && functionReturnsString(() => customFormat({
        seriesIndex,
        datapoint,
        series: formattedDataset.value,
        config: FINAL_CONFIG.value
    }))) {
        tooltipContent.value = customFormat({
            seriesIndex,
            datapoint,
            series: formattedDataset.value,
            config: FINAL_CONFIG.value
        });
    } else {
        const { 
            showValue,
            showTotal,
            totalTranslation,
            showPercentage, 
            borderColor,
            roundingValue,
            roundingPercentage 
        } = FINAL_CONFIG.value.style.chart.tooltip;

        let html = "";

        if ((timeLabels.value[seriesIndex] && timeLabels.value[seriesIndex].text) || (preciseAllTimeLabelsTooltip.value[seriesIndex] && preciseAllTimeLabelsTooltip.value[seriesIndex].text) && FINAL_CONFIG.value.style.chart.tooltip.showTimeLabel) {
            html += `<div style="width:100%;text-align:center;border-bottom:1px solid ${borderColor};padding-bottom:6px;margin-bottom:3px;">${FINAL_CONFIG.value.style.chart.tooltip.useDefaultTimeFormat ? timeLabels.value[seriesIndex]?.text : preciseAllTimeLabelsTooltip.value[seriesIndex]?.text || allTimeLabels.value[seriesIndex]?.text || ''}</div>`;
        }

        if (showTotal) {
            html += `<div class="vue-data-ui-tooltip-total" style="display:flex;flex-direction:row;align-items:center;gap:4px">
                <span>${totalTranslation}:</span>
                <span>
                    ${applyDataLabel(
                        FINAL_CONFIG.value.style.chart.bars.dataLabels.formatter,
                        sumLabel,
                        dataLabel({
                            p: FINAL_CONFIG.value.style.chart.bars.dataLabels.prefix,
                            v: sumLabel,
                            s: FINAL_CONFIG.value.style.chart.bars.dataLabels.suffix,
                            r: roundingValue
                        }),
                        { datapoint: { name: totalTranslation, value: sumLabel } }
                    )}
                </span>
            </div>`
        }

        const parenthesis = [
            showValue && showPercentage ? '(' : '',
            showValue && showPercentage ? ')' : '',
        ];

        datapoint.reverse().forEach(ds => {
            html += `
                <div style="display:flex;flex-direction:row;align-items:center;gap:4px">
                    <svg viewBox="0 0 60 60" height="14" width="14"><rect rx="5" x="0" y="0" height="60" width="60" stroke="none" fill="${FINAL_CONFIG.value.style.chart.bars.gradient.show ? `url(#gradient_${ds.id})` : ds.color}"/>${slots.pattern ? `<rect rx="5" x="0" y="0" height="60" width="60" stroke="none" fill="url(#pattern_${uid.value}_${ds.absoluteIndex})"/>` : ''}</svg>
                    ${ds.name}${showValue || showPercentage ? ':' : ''} ${showValue ? applyDataLabel(
                        FINAL_CONFIG.value.style.chart.bars.dataLabels.formatter,
                        ds.value,
                            dataLabel({
                            p: FINAL_CONFIG.value.style.chart.bars.dataLabels.prefix,
                            v: ds.value,
                            s: FINAL_CONFIG.value.style.chart.bars.dataLabels.suffix,
                            r: roundingValue,
                        }, { datapoint: ds })
                    ) : ''} ${parenthesis[0]}${showPercentage ? dataLabel({
                        v: isNaN(ds.value / sum) ? 0 : Math.abs(ds.value) / sum * 100, // Negs are absed to show relative proportion to absolute total. It's opinionated.
                        s: '%',
                        r: roundingPercentage,
                    }) : ''}${parenthesis[1]}
                </div>
            `
        });

        tooltipContent.value = `<div>${html}</div>`;
    }
}

const WIDTH = computed(() => defaultSizes.value.width);
const HEIGHT = computed(() => defaultSizes.value.height);

useTimeLabelCollision({
    timeLabelsEls: FINAL_CONFIG.value.orientation === 'vertical' ? timeLabelsEls : scaleLabels,
    timeLabels,
    slicer,
    configRef: FINAL_CONFIG,
    rotationPath: ['style', 'chart', 'grid', 'x', 'timeLabels', 'rotation'],
    autoRotatePath: ['style', 'chart', 'grid', 'x', 'timeLabels', 'autoRotate', 'enable'],
    isAutoSize: false,
    width: WIDTH,
    height: HEIGHT,
    rotation: FINAL_CONFIG.value.style.chart.grid.x.timeLabels.autoRotate.angle
});

function toggleFullscreen(state) {
    isFullscreen.value = state;
    step.value += 1;
}

function toggleTable() {
    mutableConfig.value.showTable = !mutableConfig.value.showTable;
}

function toggleLabels() {
    mutableConfig.value.dataLabels.show = !mutableConfig.value.dataLabels.show;
}

function toggleTooltip() {
    mutableConfig.value.showTooltip = !mutableConfig.value.showTooltip;
}

function getData() {
    return formattedDataset.value
}

const tableCsv = computed(() => {
    if(formattedDataset.value.length === 0) return { head: [], body: [], config: {}, columnNames: []};

    const head = formattedDataset.value.map(({name, color}) => {
        return {
            label: name,
            color
        }
    });
    const body = [];

    timeLabels.value.forEach((tl) => {
        const row = [FINAL_CONFIG.value.style.chart.grid.x.timeLabels.values[tl.absoluteIndex] ? tl.text : i + 1];
        unmutableDataset.value.forEach(s => {
            row.push(Number((s.series[tl.absoluteIndex] || 0).toFixed(FINAL_CONFIG.value.table.td.roundingValue)));
        });
        body.push(row);
    })
    return { head, body };
})

function generateCsv(callback=null) {
    const title = [[FINAL_CONFIG.value.style.chart.title.text], [FINAL_CONFIG.value.style.chart.title.subtitle.text], [""]];
    const head = ["",...tableCsv.value.head.map(h => h.label)];
    const body = tableCsv.value.body;
    const table = title.concat([head]).concat(body);
    const csvContent = createCsvContent(table);

    if (!callback) {
        downloadCsv({ csvContent, title: FINAL_CONFIG.value.style.chart.title.text || 'vue-ui-stackbar'});
    } else {
        callback(csvContent);
    }

}

const dataTable = computed(() => {
    const head = [''].concat(formattedDataset.value.map(ds => ds.name)).concat(` <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 16v2a1 1 0 0 1 -1 1h-11l6 -7l-6 -7h11a1 1 0 0 1 1 1v2" /></svg>`);

    let body = [];

    for (let i = 0; i < maxSeries.value; i += 1) {
        const sum = formattedDataset.value.map(ds => {
            return ds.series[i] ?? 0
        }).reduce((a,b ) => a + b, 0);

        body.push([FINAL_CONFIG.value.style.chart.grid.x.timeLabels.values.slice(slicer.value.start, slicer.value.end)[i] ? timeLabels.value[i].text : i+1].concat(formattedDataset.value.map(ds => (ds.series[i] ?? 0).toFixed(FINAL_CONFIG.value.table.td.roundingValue))).concat((sum ?? 0).toFixed(FINAL_CONFIG.value.table.td.roundingValue)));
    }

    const config = {
        th: {
            backgroundColor: FINAL_CONFIG.value.table.th.backgroundColor,
            color: FINAL_CONFIG.value.table.th.color,
            outline: FINAL_CONFIG.value.table.th.outline
        },
        td: {
            backgroundColor: FINAL_CONFIG.value.table.td.backgroundColor,
            color: FINAL_CONFIG.value.table.td.color,
            outline: FINAL_CONFIG.value.table.td.outline
        },
        breakpoint: FINAL_CONFIG.value.table.responsiveBreakpoint
    }

    const colNames = [FINAL_CONFIG.value.table.columnNames.period].concat(formattedDataset.value.map(ds => ds.name)).concat(FINAL_CONFIG.value.table.columnNames.total);

    return { head, body: body.slice(0, slicer.value.end - slicer.value.start), config, colNames }
});

function segregate(item) {
    if (segregated.value.includes(item.id)) {
        segregated.value = segregated.value.filter(el => el !== item.id);
    } else {
        if ( segregated.value.length === unmutableDataset.value.length - 1) return; 
        segregated.value.push(item.id);
    }
    emit('selectLegend', formattedDataset.value)
}

function validSeriesToToggle(name) {
    if (!unmutableDataset.value.length) {
        if (FINAL_CONFIG.value.debug) {
            console.warn('VueUiStackbar - There are no series to show.');
        }
        return null;
    }
    const dp = unmutableDataset.value.find(d => d.name === name);
    if (!dp) {
        if (FINAL_CONFIG.value.debug) {
            console.warn(`VueUiStackbar - Series name not found "${name}"`);
        }
        return null;
    }
    return dp;
}

function showSeries(name) {
    const dp = validSeriesToToggle(name);
    if (dp === null) return;
    if (segregated.value.includes(dp.id)) {
        segregate({ id : dp.id });
    }
}

function hideSeries(name) {
    const dp  = validSeriesToToggle(name);
    if (dp === null) return;
    if (!segregated.value.includes(dp.id))  {
        segregate({ id: dp.id });
    }
}

const legendSet = computed(() => {
    return unmutableDataset.value.map((ds, i) => {
        return {
            ...ds,
            shape: 'square',
        }
    }).map((ds) => {
        return {
            ...ds,
            opacity: segregated.value.includes(ds.id) ? 0.5 : 1,
            segregate: () => segregate(ds),
            isSegregated: segregated.value.includes(ds.id)
        }
    });
});

const legendConfig = computed(() => {
    return {
        cy: 'stackbar-legend',
        backgroundColor: FINAL_CONFIG.value.style.chart.legend.backgroundColor,
        color: FINAL_CONFIG.value.style.chart.legend.color,
        fontSize: FINAL_CONFIG.value.style.chart.legend.fontSize,
        paddingBottom: 12,
        fontWeight: FINAL_CONFIG.value.style.chart.legend.bold ? 'bold' : ''
    }
});

const isAnnotator = ref(false);
function toggleAnnotator() {
    isAnnotator.value = !isAnnotator.value;
}

function selectTimeLabel(label, relativeIndex) {
    const datapoint = JSON.parse(JSON.stringify(formattedDataset.value)).map(fd => {
        return {
            name: fd.name,
            value: fd.series[relativeIndex] === 0 ? 0 : (fd.signedSeries[relativeIndex] === -1 ? (fd.series[relativeIndex] >= 0 ? -fd.series[relativeIndex] : fd.series[relativeIndex]) : fd.series[relativeIndex]) || null,
            proportion: fd.proportions[relativeIndex] || null,
            color: fd.color,
            id: fd.id
        }
    });

    emit('selectTimeLabel', {
        datapoint,
        absoluteIndex: label.absoluteIndex,
        label: label.text
    });
}

const maxCurrentValue = computed(() => {
    return Math.max(...formattedDataset.value.flatMap(ds => ds.series))
})

function isLabelDisplayed(value, proportion) {
    if (FINAL_CONFIG.value.style.chart.bars.showDistributedPercentage && FINAL_CONFIG.value.style.chart.bars.distributed) {
        if (!canHideSmallPercentages.value) {
            return FINAL_CONFIG.value.style.chart.bars.dataLabels.hideEmptyPercentages ? proportion > 0 : true;
        }
        return proportion * 100 >= FINAL_CONFIG.value.style.chart.bars.dataLabels.hideUnderPercentage;
    } else {
        if (canHideSmallPercentages.value) {
            if (canHideSmallValues.value && debug.value) {
                console.warn('Vue Data UI - VueUiStackbar - You cannot set both dataLabels.hideUnderPercentage and dataLabels.hideUnderValue. Note that dataLabels.hideUnderPercentage takes precedence in this case.')
            }
            return value > maxCurrentValue.value * FINAL_CONFIG.value.style.chart.bars.dataLabels.hideUnderPercentage / 100;
        }
        if (!canHideSmallValues.value) {
            return FINAL_CONFIG.value.style.chart.bars.dataLabels.hideEmptyValues ? value !== 0 : true
        }
        return Math.abs(value) >= FINAL_CONFIG.value.style.chart.bars.dataLabels.hideUnderValue
    }
}

async function getImage({ scale = 2} = {}) {
    if (!stackbarChart.value) return;
    const { width, height } = stackbarChart.value.getBoundingClientRect();
    const aspectRatio = width / height; 
    const { imageUri, base64 } = await img(({ domElement: stackbarChart.value, base64: true, img: true, scale}))
    return { 
        imageUri, 
        base64, 
        title: FINAL_CONFIG.value.style.chart.title.text,
        width,
        height,
        aspectRatio
    }
}

const tableComponent = computed(() => {
    const useDialog = FINAL_CONFIG.value.table.useDialog && !FINAL_CONFIG.value.table.show;
    const open = mutableConfig.value.showTable;
    return {
        component: useDialog ? BaseDraggableDialog : Accordion,
        title: `${FINAL_CONFIG.value.style.chart.title.text}${FINAL_CONFIG.value.style.chart.title.subtitle.text ? `: ${FINAL_CONFIG.value.style.chart.title.subtitle.text}` : ''}`,
        props: useDialog ? {
            backgroundColor: FINAL_CONFIG.value.table.th.backgroundColor,
            color: FINAL_CONFIG.value.table.th.color,
            headerColor: FINAL_CONFIG.value.table.th.color,
            headerBg: FINAL_CONFIG.value.table.th.backgroundColor,
            isFullscreen: isFullscreen.value,
            fullscreenParent: stackbarChart.value,
            forcedWidth: Math.min(800, window.innerWidth * 0.8)
        } : {
            hideDetails: true,
            config: {
                open,
                maxHeight: 10000,
                body: {
                    backgroundColor: FINAL_CONFIG.value.style.chart.backgroundColor,
                    color: FINAL_CONFIG.value.style.chart.color
                },
                head: {
                    backgroundColor: FINAL_CONFIG.value.style.chart.backgroundColor,
                    color: FINAL_CONFIG.value.style.chart.color
                }
            }
        }
    }
});

watch(() => mutableConfig.value.showTable, v => {
    if (FINAL_CONFIG.value.table.show) return;
    if (v && FINAL_CONFIG.value.table.useDialog && tableUnit.value) {
        tableUnit.value.open()
    } else {
        if ('close' in tableUnit.value) {
            tableUnit.value.close()
        }
    }
});

function closeTable() {
    mutableConfig.value.showTable = false;
    if (userOptionsRef.value) {
        userOptionsRef.value.setTableIconState(false);
    }
}

const svgBg = computed(() => FINAL_CONFIG.value.style.chart.backgroundColor);
const svgLegend = computed(() => FINAL_CONFIG.value.style.chart.legend);
const svgTitle = computed(() => FINAL_CONFIG.value.style.chart.title);

const { exportSvg, getSvg } = useSvgExport({
    svg: svgRef,
    title: svgTitle,
    legend: svgLegend,
    legendItems: legendSet,
    backgroundColor: svgBg
})

async function generateSvg({ isCb }) {
    if (isCb) {
        const { blob, url, text, dataUrl } = await getSvg();
        FINAL_CONFIG.value.userOptions.callbacks.svg({ blob, url, text, dataUrl })

    } else {
        exportSvg();
    }
}

function validSlicerEnd(v) {
    const _max = maxSeries.value;

    if (v > _max) {
        return _max;
    }
    if (v < 0 || (v < slicer.value.start)) {
        if (FINAL_CONFIG.value.style.chart.zoom.startIndex !== null) {
            return slicer.value.start + 1
        } else {
            return 1
        }
    }
    return v;
}

const isSettingUp = ref(false);
const slicerReady = ref(false);

function setupSlicer() {
    if (isSettingUp.value) return;
    isSettingUp.value = true;
    try {
        const { startIndex, endIndex } = FINAL_CONFIG.value.style.chart.zoom;
        const max = maxSeries.value;

        const start = startIndex != null ? startIndex : 0;
        const end = endIndex   != null ? Math.min(validSlicerEnd(endIndex + 1), max) : max;

        suppressChild.value = true;
        slicer.value.start = start;
        slicer.value.end   = end;
        slicerPrecog.value.start = start;
        slicerPrecog.value.end   = end;
        normalizeSlicerWindow();
        slicerReady.value = true;
    } finally {
        queueMicrotask(() => { suppressChild.value = false; });
        isSettingUp.value = false;
    }
}

const suppressChild = ref(false);

function onSlicerStart(v) {
    if (isSettingUp.value || suppressChild.value) return;
    if (v === slicer.value.start) return;
    slicer.value.start = v;
    slicerPrecog.value.start = v;
    normalizeSlicerWindow();
}

function onSlicerEnd(v) {
    if (isSettingUp.value || suppressChild.value) return;
    const end = validSlicerEnd(v);
    if (end === slicer.value.end) return;
    slicer.value.end = end;
    slicerPrecog.value.end = end;
    normalizeSlicerWindow();
}

function selectX({ seriesIndex, datapoint }) {
    const index = slicer.value.start + seriesIndex
    emit('selectX', {
        dataset: datapoint,
        index,
        indexLabel: FINAL_CONFIG.value.style.chart.grid.x.timeLabels.values[index]
    })
}

function getZeroPositions() {
    const y0 = yLabels.value?.[0]?.zero ?? drawingArea.value.bottom;
    return { y0 };
}

function placeLabelTotalY(index) {
    const { y0 } = getZeroPositions();
    const cfg = FINAL_CONFIG.value.style.chart.bars.totalValues;
    const pad = Math.max(2, (cfg.fontSize * 0.3) + cfg.offsetY);

    let minY = Infinity;
    let hasPos = false;

    for (const dp of formattedDataset.value || []) {
        const v = dp?.series?.[index] ?? 0;
        const h = dp?.height?.[index] ?? 0;
        const y = dp?.y?.[index];
        if (v > 0 && h > 0 && Number.isFinite(y)) {
        hasPos = true;
        if (y < minY) minY = y;
        }
    }

    const topY = hasPos && Number.isFinite(minY) ? minY : y0;
    const rawY = topY - pad;

    const clampedY = Math.min(
        Math.max(rawY, 0),
        drawingArea.value.bottom
    );

    return clampedY;
}



function placeLabelTotalX(index) {
    const { x0 } = getZeroPositions();
    const pad = Math.max(2, (FINAL_CONFIG.value.style.chart.bars.totalValues.fontSize * 0.3) + FINAL_CONFIG.value.style.chart.bars.totalValues.offsetX);

    let rightMost = -Infinity;
    let hasPos = false;

    for (const dp of formattedDataset.value || []) {
        const v = dp?.series?.[index] ?? 0;
        const x = dp?.horizontal_x?.[index];
        const wRaw = dp?.horizontal_width?.[index];
        const w = Number.isFinite(wRaw) ? Math.max(0, wRaw) : 0;
        if (!Number.isFinite(x)) continue;

        if (v > 0 && w > 0) {
        hasPos = true;
        rightMost = Math.max(rightMost, x + w);
        }
    }

    const baseX = hasPos && Number.isFinite(rightMost) ? rightMost : x0;
    return baseX + pad;
}

defineExpose({
    getData,
    getImage,
    generatePdf,
    generateCsv,
    generateImage,
    generateSvg,
    hideSeries,
    showSeries,
    toggleTable,
    toggleLabels,
    toggleTooltip,
    toggleAnnotator,
    toggleFullscreen
});

</script>

<template>
    <div 
        :id="`stackbar_${uid}`"
        ref="stackbarChart"
        :class="{'vue-data-ui-component': true, 'vue-ui-stackbar': true, 'vue-data-ui-wrapper-fullscreen' : isFullscreen }" 
        :style="`background:${FINAL_CONFIG.style.chart.backgroundColor};color:${FINAL_CONFIG.style.chart.color};font-family:${FINAL_CONFIG.style.fontFamily}; position: relative; ${FINAL_CONFIG.responsive ? 'height: 100%' : ''}`"
        @mouseenter="onChartEnter" 
        @mouseleave="onChartLeave"
    >
        <PenAndPaper 
            v-if="FINAL_CONFIG.userOptions.buttons.annotator"
            :svgRef="svgRef"
            :backgroundColor="FINAL_CONFIG.style.chart.backgroundColor"
            :color="FINAL_CONFIG.style.chart.color"
            :active="isAnnotator"
            @close="toggleAnnotator"
        >
            <template #annotator-action-close>
                <slot name="annotator-action-close"/>
            </template>
            <template #annotator-action-color="{ color }">
                <slot name="annotator-action-color" v-bind="{ color }"/>
            </template>
            <template #annotator-action-draw="{ mode }">
                <slot name="annotator-action-draw" v-bind="{ mode }"/>
            </template>
            <template #annotator-action-undo="{ disabled }">
                <slot name="annotator-action-undo" v-bind="{ disabled }"/>
            </template>
            <template #annotator-action-redo="{ disabled }">
                <slot name="annotator-action-redo" v-bind="{ disabled }"/>
            </template>
            <template #annotator-action-delete="{ disabled }">
                <slot name="annotator-action-delete" v-bind="{ disabled }"/>
            </template>
        </PenAndPaper>

        <slot name="userConfig"/>

        <div ref="chartTitle" v-if="FINAL_CONFIG.style.chart.title.text" :style="`width:100%;background:transparent;padding-bottom:24px`">
            <Title
                :key="`title_${titleStep}`"
                :config="{
                    title: {
                        cy: 'stackbar-title',
                        ...FINAL_CONFIG.style.chart.title,
                    },
                    subtitle: {
                        cy: 'stackbar-subtitle',
                        ...FINAL_CONFIG.style.chart.title.subtitle
                    }
                }"
            />
        </div>

        <div :id="`legend-top-${uid}`" />

        <UserOptions
            ref="userOptionsRef"
            :key="`user_option_${step}`"
            v-if="FINAL_CONFIG.userOptions.show && isDataset && (keepUserOptionState ? true : userOptionsVisible)"
            :backgroundColor="FINAL_CONFIG.style.chart.backgroundColor"
            :color="FINAL_CONFIG.style.chart.color"
            :isPrinting="isPrinting"
            :isImaging="isImaging"
            :uid="uid"
            :hasTooltip="FINAL_CONFIG.style.chart.tooltip.show && FINAL_CONFIG.userOptions.buttons.tooltip"
            :hasPdf="FINAL_CONFIG.userOptions.buttons.pdf"
            :hasImg="FINAL_CONFIG.userOptions.buttons.img"
            :hasSvg="FINAL_CONFIG.userOptions.buttons.svg"
            :hasXls="FINAL_CONFIG.userOptions.buttons.csv"
            :hasTable="FINAL_CONFIG.userOptions.buttons.table"
            :hasLabel="FINAL_CONFIG.userOptions.buttons.labels"
            :hasFullscreen="FINAL_CONFIG.userOptions.buttons.fullscreen"
            :isFullscreen="isFullscreen"
            :chartElement="stackbarChart"
            :position="FINAL_CONFIG.userOptions.position"
            :isTooltip="mutableConfig.showTooltip"
            :titles="{...FINAL_CONFIG.userOptions.buttonTitles }"
            :hasAnnotator="FINAL_CONFIG.userOptions.buttons.annotator"
            :isAnnotation="isAnnotator"
            :callbacks="FINAL_CONFIG.userOptions.callbacks"
            :printScale="FINAL_CONFIG.userOptions.print.scale"
            :tableDialog="FINAL_CONFIG.table.useDialog"
            @toggleFullscreen="toggleFullscreen"
            @generatePdf="generatePdf"
            @generateCsv="generateCsv"
            @generateImage="generateImage"
            @generateSvg="generateSvg"
            @toggleTable="toggleTable"
            @toggleLabels="toggleLabels"
            @toggleTooltip="toggleTooltip"
            @toggleAnnotator="toggleAnnotator"
            :style="{
                visibility: keepUserOptionState ? userOptionsVisible ? 'visible' : 'hidden' : 'visible'
            }"
        >
            <template #menuIcon="{ isOpen, color }" v-if="$slots.menuIcon">
                <slot name="menuIcon" v-bind="{ isOpen, color }"/>
            </template>
            <template #optionTooltip v-if="$slots.optionTooltip">
                <slot name="optionTooltip"/>
            </template>
            <template #optionPdf v-if="$slots.optionPdf">
                <slot name="optionPdf" />
            </template>
            <template #optionCsv v-if="$slots.optionCsv">
                <slot name="optionCsv" />
            </template>
            <template #optionImg v-if="$slots.optionImg">
                <slot name="optionImg" />
            </template>
            <template #optionSvg v-if="$slots.optionSvg">
                <slot name="optionSvg" />
            </template>
            <template #optionTable v-if="$slots.optionTable">
                <slot name="optionTable" />
            </template>
            <template #optionLabels v-if="$slots.optionLabels">
                <slot name="optionLabels" />
            </template>
            <template v-if="$slots.optionFullscreen" template #optionFullscreen="{ toggleFullscreen, isFullscreen }">
                <slot name="optionFullscreen" v-bind="{ toggleFullscreen, isFullscreen }"/>
            </template>
            <template v-if="$slots.optionAnnotator" #optionAnnotator="{ toggleAnnotator, isAnnotator }">
                <slot name="optionAnnotator" v-bind="{ toggleAnnotator, isAnnotator }" />
            </template>
        </UserOptions>

        <svg
            ref="svgRef"
            :xmlns="XMLNS"
            :viewBox="`0 0 ${drawingArea.chartWidth <= 0 ? 10 : drawingArea.chartWidth} ${drawingArea.chartHeight <= 0 ? 10 : drawingArea.chartHeight}`"
            :class="{ 'vue-data-ui-loading' : loading }"
            :style="`max-width:100%;overflow:visible;background:transparent;color:${FINAL_CONFIG.style.chart.color}`"
        >
            <PackageVersion />

            <!-- BACKGROUND SLOT -->
            <foreignObject 
                v-if="$slots['chart-background']"
                :x="drawingArea.left"
                :y="drawingArea.top"
                :width="drawingArea.width <= 0 ? 10 : drawingArea.width"
                :height="drawingArea.height <= 0 ? 10 : drawingArea.height"
                :style="{
                    pointerEvents: 'none'
                }"
            >
                <slot name="chart-background"/>
            </foreignObject>
            
            <!-- GRADIENT DEFS -->
            <defs v-if="FINAL_CONFIG.style.chart.bars.gradient.show">
                <linearGradient v-for="(ds, i) in formattedDataset" :id="`gradient_${ds.id}`" x1="0%" y1="0%" x2="0%" y2="100%">
                    <stop offset="0%" :stop-color="ds.color"/>
                    <stop offset="61.8%" :stop-color="lightenHexColor(ds.color, FINAL_CONFIG.style.chart.bars.gradient.intensity / 100)"/>
                    <stop offset="100%" :stop-color="ds.color"/>
                </linearGradient>
            </defs>

            <!-- FRAME -->
            <rect 
                data-cy="frame" 
                v-if="FINAL_CONFIG.style.chart.grid.frame.show"
                :style="{ pointerEvents: 'none', transition: 'none', animation: 'none !important' }"
                :x="Math.max(0, drawingArea.left)" 
                :y="Math.max(0, drawingArea.top)"
                :width="Math.max(0, drawingArea.width)"
                :height="Math.max(0, drawingArea.height)" 
                fill="transparent"
                :stroke="FINAL_CONFIG.style.chart.grid.frame.stroke"
                :stroke-width="FINAL_CONFIG.style.chart.grid.frame.strokeWidth"
                :stroke-linecap="FINAL_CONFIG.style.chart.grid.frame.strokeLinecap"
                :stroke-linejoin="FINAL_CONFIG.style.chart.grid.frame.strokeLinejoin"
                :stroke-dasharray="FINAL_CONFIG.style.chart.grid.frame.strokeDasharray" 
            />

            <!-- HORIZONTAL LINES (vertical mode) -->
            <template v-if="FINAL_CONFIG.style.chart.grid.x.showHorizontalLines && FINAL_CONFIG.orientation === 'vertical'">
                <line
                    v-for="(yLabel, i) in yLabels"
                    :x1="drawingArea.left"
                    :x2="drawingArea.right"
                    :y1="yLabel.y"
                    :y2="yLabel.y"
                    :stroke="FINAL_CONFIG.style.chart.grid.x.linesColor"
                    :stroke-width="FINAL_CONFIG.style.chart.grid.x.linesThickness"
                    :stroke-dasharray="FINAL_CONFIG.style.chart.grid.x.linesStrokeDasharray"
                    stroke-linecap="round"
                />
            </template>

            <!-- HORIZONTAL LINES (horizontal mode) -->
            <template v-if="FINAL_CONFIG.style.chart.grid.x.showHorizontalLines && FINAL_CONFIG.orientation === 'horizontal'">
                <line
                    v-for="(_, i) in (slicer.end - slicer.start + 1)"
                    :x1="drawingArea.left"
                    :x2="drawingArea.right"
                    :y1="drawingArea.top + (barSlot * i)"
                    :y2="drawingArea.top + (barSlot * i)"
                    :stroke="FINAL_CONFIG.style.chart.grid.x.linesColor"
                    :stroke-width="FINAL_CONFIG.style.chart.grid.x.linesThickness"
                    :stroke-dasharray="FINAL_CONFIG.style.chart.grid.x.linesStrokeDasharray"
                    stroke-linecap="round"
                />
            </template>

            <!-- VERTICAL LINES (vertical mode) -->
            <template v-if="FINAL_CONFIG.style.chart.grid.y.showVerticalLines && FINAL_CONFIG.orientation === 'vertical'">
                <line
                    v-for="(_, i) in (slicer.end - slicer.start + 1)"
                    :x1="drawingArea.left + (barSlot * i)"
                    :x2="drawingArea.left + (barSlot * i)"
                    :y1="drawingArea.top"
                    :y2="drawingArea.bottom"
                    :stroke="FINAL_CONFIG.style.chart.grid.y.linesColor"
                    :stroke-width="FINAL_CONFIG.style.chart.grid.y.linesThickness"
                    :stroke-dasharray="FINAL_CONFIG.style.chart.grid.y.linesStrokeDasharray"
                    stroke-linecap="round"
                />
            </template>

            <!-- VERTICAL LINES (horizontal mode) -->
            <template v-if="FINAL_CONFIG.style.chart.grid.y.showVerticalLines && FINAL_CONFIG.orientation === 'horizontal'">
                <line
                    v-for="(yLabel, i) in yLabels"
                    :x1="yLabel.horizontal_x"
                    :x2="yLabel.horizontal_x"
                    :y1="drawingArea.top"
                    :y2="drawingArea.bottom"
                    :stroke="FINAL_CONFIG.style.chart.grid.y.linesColor"
                    :stroke-width="FINAL_CONFIG.style.chart.grid.y.linesThickness"
                    :stroke-dasharray="FINAL_CONFIG.style.chart.grid.y.linesStrokeDasharray"
                    stroke-linecap="round"
                />
            </template>

            <g v-for="(dp, i) in formattedDataset">

                <defs v-if="$slots.pattern">
                    <slot name="pattern" v-bind="{ seriesIndex: dp.absoluteIndex, patternId: `pattern_${uid}_${dp.absoluteIndex}`}"/>
                </defs>

                <!-- STACKED BARS (vertical mode) -->
                <template v-if="FINAL_CONFIG.orientation === 'vertical'">
                    <rect 
                        v-for="(rect, j) in dp.x"
                        :x="rect"
                        :y="forceValidValue(dp.y[j])"
                        :height="dp.height[j] < 0 ? 0.0001 : dp.height[j] || 0"
                        :rx="FINAL_CONFIG.style.chart.bars.borderRadius > dp.height[j] / 2 ? (dp.height[j] < 0 ? 0 : dp.height[j]) / 2 : FINAL_CONFIG.style.chart.bars.borderRadius "
                        :width="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
                        :fill="FINAL_CONFIG.style.chart.bars.gradient.show ? `url(#gradient_${dp.id})` : dp.color"
                        :stroke="FINAL_CONFIG.style.chart.backgroundColor"
                        :stroke-width="FINAL_CONFIG.style.chart.bars.strokeWidth"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        :class="{ 'vue-data-ui-bar-animated': FINAL_CONFIG.useCssAnimation, 'vue-data-ui-bar-transition': isLoaded }"
                    />     
                    <g v-if="$slots.pattern">
                        <rect 
                        v-for="(rect, j) in dp.x"
                        :x="rect"
                        :y="forceValidValue(dp.y[j])"
                        :height="dp.height[j] < 0 ? 0.0001 : dp.height[j] || 0"
                        :rx="FINAL_CONFIG.style.chart.bars.borderRadius > dp.height[j] / 2 ? (dp.height[j] < 0 ? 0 : dp.height[j]) / 2 : FINAL_CONFIG.style.chart.bars.borderRadius "
                        :width="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
                        :fill="`url(#pattern_${uid}_${dp.absoluteIndex})`"
                        :stroke="FINAL_CONFIG.style.chart.backgroundColor"
                        :stroke-width="FINAL_CONFIG.style.chart.bars.strokeWidth"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        :class="{ 'vue-data-ui-bar-animated': FINAL_CONFIG.useCssAnimation, 'vue-data-ui-bar-transition': isLoaded }"
                    /> 
                    </g>      
                </template>

                <!-- STACKED BARS (horizontal mode) -->
                <template v-else>
                    <rect 
                        v-for="(rect, j) in dp.horizontal_x"
                        :x="forceValidValue(rect, drawingArea.left)"
                        :y="dp.horizontal_y[j] < 0 ? 0 : dp.horizontal_y[j]"
                        :width="forceValidValue(dp.horizontal_width[j] < 0 ? 0.0001 : dp.horizontal_width[j])"
                        :rx="FINAL_CONFIG.style.chart.bars.borderRadius > dp.height[j] / 2 ? (dp.height[j] < 0 ? 0 : dp.height[j]) / 2 : FINAL_CONFIG.style.chart.bars.borderRadius "
                        :height="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
                        :fill="FINAL_CONFIG.style.chart.bars.gradient.show ? `url(#gradient_${dp.id})` : dp.color"
                        :stroke="FINAL_CONFIG.style.chart.backgroundColor"
                        :stroke-width="FINAL_CONFIG.style.chart.bars.strokeWidth"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        :class="{ 'vue-data-ui-bar-animated': FINAL_CONFIG.useCssAnimation, 'vue-data-ui-bar-transition': isLoaded }"
                    />
                    <g v-if="$slots.pattern">
                        <rect 
                            v-for="(rect, j) in dp.horizontal_x"
                            :x="forceValidValue(rect, drawingArea.left)"
                            :y="dp.horizontal_y[j] < 0 ? 0 : dp.horizontal_y[j]"
                            :width="forceValidValue(dp.horizontal_width[j] < 0 ? 0.0001 : dp.horizontal_width[j])"
                            :rx="FINAL_CONFIG.style.chart.bars.borderRadius > dp.height[j] / 2 ? (dp.height[j] < 0 ? 0 : dp.height[j]) / 2 : FINAL_CONFIG.style.chart.bars.borderRadius "
                            :height="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
                            :fill="`url(#pattern_${uid}_${dp.absoluteIndex})`"
                            :stroke="FINAL_CONFIG.style.chart.backgroundColor"
                            :stroke-width="FINAL_CONFIG.style.chart.bars.strokeWidth"
                            stroke-linecap="round"
                            stroke-linejoin="round"
                            :class="{ 'vue-data-ui-bar-animated': FINAL_CONFIG.useCssAnimation, 'vue-data-ui-bar-transition': isLoaded }"
                        />
                    </g>    
                </template>
            </g>

            <!-- X AXIS -->
            <line
                data-cy="line-axis-x"
                v-if="FINAL_CONFIG.style.chart.grid.x.showAxis"
                :x1="drawingArea.left"
                :x2="drawingArea.right"
                :y1="drawingArea.bottom"
                :y2="drawingArea.bottom"
                :stroke="FINAL_CONFIG.style.chart.grid.x.axisColor"
                :stroke-width="FINAL_CONFIG.style.chart.grid.x.axisThickness"
                stroke-linecap="round"
                stroke-linejoin="round"
            />

            <!-- Y AXIS -->
            <line
                data-cy="line-axis-y"
                v-if="FINAL_CONFIG.style.chart.grid.y.showAxis && !FINAL_CONFIG.style.chart.bars.distributed"
                :x1="drawingArea.left"
                :x2="drawingArea.left"
                :y1="drawingArea.top"
                :y2="drawingArea.bottom"
                :stroke="FINAL_CONFIG.style.chart.grid.y.axisColor"
                :stroke-width="FINAL_CONFIG.style.chart.grid.y.axisThickness"
                stroke-linecap="round"
                stroke-linejoin="round"
            />

            <!-- X AXIS LABEL -->
            <text
                ref="xAxisLabel"
                data-cy="axis-label-x"
                v-if="FINAL_CONFIG.style.chart.grid.x.axisName.show && FINAL_CONFIG.style.chart.grid.x.axisName.text"
                :x="drawingArea.left + (drawingArea.width / 2)"
                :y="drawingArea.chartHeight - 3"
                :font-size="FINAL_CONFIG.style.chart.grid.x.axisName.fontSize"
                :fill="FINAL_CONFIG.style.chart.grid.x.axisName.color"
                :font-weight="FINAL_CONFIG.style.chart.grid.x.axisName.bold ? 'bold': 'normal'"
                text-anchor="middle"
            >
                {{ FINAL_CONFIG.style.chart.grid.x.axisName.text }}
            </text>

            <!-- Y AXIS LABEL -->
            <text
                ref="yAxisLabel"
                data-cy="axis-label-y"
                v-if="FINAL_CONFIG.style.chart.grid.y.axisName.show && FINAL_CONFIG.style.chart.grid.y.axisName.text"
                :transform="`translate(${FINAL_CONFIG.style.chart.grid.y.axisName.fontSize}, ${drawingArea.top + (drawingArea.height / 2)}) rotate(-90)`"
                :font-size="FINAL_CONFIG.style.chart.grid.y.axisName.fontSize"
                :fill="FINAL_CONFIG.style.chart.grid.y.axisName.color"
                :font-weight="FINAL_CONFIG.style.chart.grid.y.axisName.bold ? 'bold': 'normal'"
                text-anchor="middle"
            >
                {{ FINAL_CONFIG.style.chart.grid.y.axisName.text }}
            </text>

            <!-- RECT DATA LABELS (vertical mode) -->
            <template v-if="mutableConfig.dataLabels.show && FINAL_CONFIG.orientation === 'vertical'">
                <g v-for="(dp, i) in formattedDataset">            
                    <!-- RECT LABELS -->
                    <template v-for="(rect, j) in dp.x">                    
                        <text
                            data-cy="label-datapoint"
                            v-if="isLabelDisplayed(dp.series[j], dp.proportions[j])"
                            :x="rect + (barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2) / 2)"
                            :y="dp.y[j] + dp.height[j] / 2 + FINAL_CONFIG.style.chart.bars.dataLabels.fontSize / 3"
                            :font-size="FINAL_CONFIG.style.chart.bars.dataLabels.fontSize"
                            :fill="FINAL_CONFIG.style.chart.bars.dataLabels.adaptColorToBackground ? adaptColorToBackground(dp.color) : FINAL_CONFIG.style.chart.bars.dataLabels.color"
                            :font-weight="FINAL_CONFIG.style.chart.bars.dataLabels.bold ? 'bold' : 'normal'"
                            text-anchor="middle"
                        >
                            {{ FINAL_CONFIG.style.chart.bars.showDistributedPercentage && FINAL_CONFIG.style.chart.bars.distributed ? 
                                barDataLabelPercentage(dp.proportions[j] * 100, dp, i, j) : 
                                barDataLabel(dp.series[j], dp, i, j, dp.signedSeries[j]) }}
                        </text>
                    </template>
                </g>

                <!-- RECT TOTAL LABELS -->
                <g ref="sumTop" v-if="FINAL_CONFIG.style.chart.bars.totalValues.show && formattedDataset.length > 1">
                    <template v-for="(total, i) in totalLabels">
                        <text
                            data-cy="label-total"
                            v-if="FINAL_CONFIG.style.chart.bars.dataLabels.hideEmptyValues ? total.value !== 0 : true"
                            :x="drawingArea.left + (barSlot * i) + barSlot / 2"
                            :y="placeLabelTotalY(i)"
                            text-anchor="middle"
                            :font-size="FINAL_CONFIG.style.chart.bars.totalValues.fontSize"
                            :font-weight="FINAL_CONFIG.style.chart.bars.totalValues.bold ? 'bold' : 'normal'"
                            :fill="FINAL_CONFIG.style.chart.bars.totalValues.color"
                        >
                            {{ barDataLabel(total.value, total, i, total.sign) }}
                        </text>
                    </template>
                </g>
            </template>

            <!-- RECT DATA LABELS (horizontal mode) -->
            <template v-if="mutableConfig.dataLabels.show && FINAL_CONFIG.orientation === 'horizontal'">
                <g v-for="(dp, i) in formattedDataset">            
                    <!-- RECT LABELS -->
                    <template v-for="(rect, j) in dp.horizontal_x">
                        <text
                            data-cy="label-datapoint"
                            v-if="isLabelDisplayed(dp.series[j], dp.proportions[j])"
                            :x="rect + ((dp.horizontal_width[j] < 0 ? 0.0001 : dp.horizontal_width[j]) / 2)"
                            :y="dp.horizontal_y[j] + (barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2) / 2) + (FINAL_CONFIG.style.chart.bars.dataLabels.fontSize /3)"
                            :font-size="FINAL_CONFIG.style.chart.bars.dataLabels.fontSize"
                            :fill="FINAL_CONFIG.style.chart.bars.dataLabels.adaptColorToBackground ? adaptColorToBackground(dp.color) : FINAL_CONFIG.style.chart.bars.dataLabels.color"
                            :font-weight="FINAL_CONFIG.style.chart.bars.dataLabels.bold ? 'bold' : 'normal'"
                            text-anchor="middle"
                        >
                            {{ FINAL_CONFIG.style.chart.bars.showDistributedPercentage && FINAL_CONFIG.style.chart.bars.distributed ? 
                                barDataLabelPercentage(dp.proportions[j] * 100, dp, i, j) : 
                                barDataLabel(dp.series[j], dp, i, j, dp.signedSeries[j]) }}
                        </text>
                    </template>
                </g>
                <!-- RECT TOTAL LABELS -->
                <g ref="sumRight" v-if="FINAL_CONFIG.style.chart.bars.totalValues.show && formattedDataset.length > 1">
                    <template v-for="(total, i) in totalLabels">
                        <text
                            data-cy="label-total"
                            v-if="FINAL_CONFIG.style.chart.bars.dataLabels.hideEmptyValues ? total.value !== 0 : true"
                            :x="placeLabelTotalX(i)"
                            :y="drawingArea.top + (barSlot * i) + barSlot / 2 + (FINAL_CONFIG.style.chart.bars.totalValues.fontSize / 3)"
                            text-anchor="start"
                            :font-size="FINAL_CONFIG.style.chart.bars.totalValues.fontSize"
                            :font-weight="FINAL_CONFIG.style.chart.bars.totalValues.bold ? 'bold' : 'normal'"
                            :fill="FINAL_CONFIG.style.chart.bars.totalValues.color"
                        >
                            {{ barDataLabel(total.value, total, i, total.sign) }}
                        </text>
                    </template>
                </g>
            </template>

            <!-- SCALE LABELS (vertical mode) -->
            <template v-if="FINAL_CONFIG.style.chart.grid.y.axisLabels.show && !FINAL_CONFIG.style.chart.bars.distributed && FINAL_CONFIG.orientation === 'vertical'">
                <g ref="scaleLabels">
                    <line
                        data-cy="scale-line-y"
                        v-for="(yLabel, i) in yLabels"
                        :x1="drawingArea.left"
                        :x2="drawingArea.left - 6"
                        :y1="yLabel.y"
                        :y2="yLabel.y"
                        :stroke="FINAL_CONFIG.style.chart.grid.x.axisColor"
                        :stroke-width="1"
                    />
                    <text
                        data-cy="scale-label-y"
                        v-for="(yLabel, i) in yLabels"
                        :x="yLabel.x"
                        :y="yLabel.y + FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize / 3"
                        :font-size="FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize"
                        :font-weight="FINAL_CONFIG.style.chart.grid.y.axisLabels.bold ? 'bold' : 'normal'"
                        :fill="FINAL_CONFIG.style.chart.grid.y.axisLabels.color"
                        text-anchor="end"
                    >
                        {{ 
                            applyDataLabel(
                                FINAL_CONFIG.style.chart.grid.y.axisLabels.formatter,
                                yLabel.value,
                                dataLabel({
                                    p: FINAL_CONFIG.style.chart.bars.dataLabels.prefix,
                                    v: yLabel.value,
                                    s: FINAL_CONFIG.style.chart.bars.dataLabels.suffix,
                                    r: FINAL_CONFIG.style.chart.grid.y.axisLabels.rounding,
                                }),
                                { datapoint: yLabel }
                            )
                        }}
                    </text>
                </g>
            </template>

            <!-- SCALE LABELS (horizontal mode) -->
            <template v-if="FINAL_CONFIG.style.chart.grid.y.axisLabels.show && !FINAL_CONFIG.style.chart.bars.distributed && FINAL_CONFIG.orientation === 'horizontal'">
                <g ref="scaleLabels">
                    <line
                        data-cy="scale-line-y"
                        v-for="(yLabel, i) in yLabels"
                        :x1="yLabel.horizontal_x"
                        :x2="yLabel.horizontal_x"
                        :y1="drawingArea.bottom"
                        :y2="drawingArea.bottom + 6"
                        :stroke="FINAL_CONFIG.style.chart.grid.x.axisColor"
                        :stroke-width="1"
                        stroke-linecap="round"
                    />
                    <text
                        data-cy="scale-label-y"
                        class="vue-data-ui-time-label"
                        v-for="(yLabel, i) in yLabels"
                        :font-size="FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize"
                        :font-weight="FINAL_CONFIG.style.chart.grid.y.axisLabels.bold ? 'bold' : 'normal'"
                        :fill="FINAL_CONFIG.style.chart.grid.y.axisLabels.color"
                        :text-anchor="FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation > 0 ? 'start' : FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation < 0 ? 'end' : 'middle'"
                        :transform="`translate(${yLabel.horizontal_x}, ${drawingArea.bottom + FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize * 1.3 + FINAL_CONFIG.style.chart.grid.x.timeLabels.offsetY}), rotate(${FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation})`"
                    >
                        {{ 
                            applyDataLabel(
                                FINAL_CONFIG.style.chart.grid.y.axisLabels.formatter,
                                yLabel.value,
                                dataLabel({
                                    p: FINAL_CONFIG.style.chart.bars.dataLabels.prefix,
                                    v: yLabel.value,
                                    s: FINAL_CONFIG.style.chart.bars.dataLabels.suffix,
                                    r: FINAL_CONFIG.style.chart.grid.y.axisLabels.rounding,
                                }),
                                { datapoint: yLabel }
                            )
                        }}
                    </text>
                </g>
            </template>

            <!-- TIME LABELS VERTICAL-->
            <template v-if="FINAL_CONFIG.style.chart.grid.x.timeLabels.show && FINAL_CONFIG.orientation === 'vertical'">
                <g ref="timeLabelsEls">
                    <g v-if="$slots['time-label']">
                        <g v-for="(timeLabel, i) in displayedTimeLabels">
                            <slot name="time-label" v-bind="{
                                x: drawingArea.left + (barSlot * i) + barSlot / 2,
                                y: drawingArea.bottom + FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize * 1.3 + FINAL_CONFIG.style.chart.grid.x.timeLabels.offsetY,
                                fontSize: FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize,
                                fill: FINAL_CONFIG.style.chart.grid.x.timeLabels.color,
                                transform: `translate(${drawingArea.left + (barSlot * i) + barSlot / 2}, ${drawingArea.bottom + FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize * 1.3 + FINAL_CONFIG.style.chart.grid.x.timeLabels.offsetY}), rotate(${FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation})`,
                                absoluteIndex: timeLabel.absoluteIndex,
                                content: timeLabel.text,
                                textAnchor: FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation > 0 ? 'start' : FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation < 0 ? 'end' : 'middle',
                                show: true
                            }"/>
                        </g>
                    </g>
                    <g v-else>
                        <g v-for="(timeLabel, i) in displayedTimeLabels">
                            <text
                                v-if="!String(timeLabel.text).includes('\n')"
                                class="vue-data-ui-time-label"
                                :key="i"
                                data-cy="time-label"
                                :text-anchor="FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation > 0
                                    ? 'start'
                                    : FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation < 0
                                    ? 'end'
                                    : 'middle'"
                                :font-size="FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize"
                                :font-weight="FINAL_CONFIG.style.chart.grid.x.timeLabels.bold ? 'bold' : 'normal'"
                                :fill="FINAL_CONFIG.style.chart.grid.x.timeLabels.color"
                                :transform="`translate(${drawingArea.left + barSlot * i + barSlot / 2}, ${drawingArea.bottom + FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize * 1.3 + FINAL_CONFIG.style.chart.grid.x.timeLabels.offsetY}), rotate(${FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation})`"
                                style="cursor: pointer"
                                @click="() => selectTimeLabel(timeLabel, i)"
                            >
        
                                {{ timeLabel.text }}
                            </text>
                            <text
                                v-else
                                :key="i + '-multi'"
                                data-cy="time-label"
                                :text-anchor="FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation > 0
                                    ? 'start'
                                    : FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation < 0
                                    ? 'end'
                                    : 'middle'"
                                :font-size="FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize"
                                :fill="FINAL_CONFIG.style.chart.grid.x.timeLabels.color"
                                :transform="`
                                    translate(
                                    ${drawingArea.left + barSlot * i + barSlot / 2},
                                    ${drawingArea.bottom + FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize * 1.3 + FINAL_CONFIG.style.chart.grid.x.timeLabels.offsetY}
                                    ),
                                    rotate(${FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation})
                                `"
                                style="cursor: pointer"
                                v-html="createTSpansFromLineBreaksOnX({
                                    content: String(timeLabel.text),
                                    fontSize: FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize,
                                    fill: FINAL_CONFIG.style.chart.grid.x.timeLabels.color,
                                    x: 0,
                                    y: 0
                                })"
                                @click="() => selectTimeLabel(timeLabel, i)"
                            />
                        </g>
                    </g>
                </g>
            </template>

            <!-- TIME LABELS HORIZONTAL -->
            <template v-if="FINAL_CONFIG.style.chart.grid.x.timeLabels.show && FINAL_CONFIG.orientation === 'horizontal'">
                <g ref="timeLabelsEls">
                    <g v-if="$slots['time-label']">
                        <g v-for="(timeLabel, i) in timeLabels">
                            <slot name="time-label" v-bind="{
                                x: drawingArea.left - 8,
                                y: drawingArea.top + (barSlot * i ) + (barSlot / 2) + FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize / 3,
                                fontSize: FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize,
                                fill: FINAL_CONFIG.style.chart.grid.x.timeLabels.color,
                                transform: null,
                                absoluteIndex: timeLabel.absoluteIndex,
                                content: timeLabel.text,
                                textAnchor: 'end',
                                show: true
                            }" />
                        </g>
                    </g>
                    <g v-else>
                        <g v-for="(timeLabel, i) in timeLabels">
                            <text
                                v-if="!String(timeLabel.text).includes('\n')"
                                data-cy="time-label"
                                text-anchor="end"
                                :font-size="FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize"
                                :font-weight="FINAL_CONFIG.style.chart.grid.y.axisLabels.bold ? 'bold' : 'normal'"
                                :fill="FINAL_CONFIG.style.chart.grid.y.axisLabels.color"
                                :x="drawingArea.left - 8"
                                :y="drawingArea.top + (barSlot * i ) + (barSlot / 2) + FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize / 3"
                                :style="{
                                    cursor: 'pointer'
                                }"
                                @click="() => selectTimeLabel(timeLabel, i)"
                            >
                                {{ timeLabel.text }}
                            </text>
                            <text
                                v-else
                                data-cy="time-label"
                                text-anchor="end"
                                :font-size="FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize"
                                :font-weight="FINAL_CONFIG.style.chart.grid.y.axisLabels.bold ? 'bold' : 'normal'"
                                :fill="FINAL_CONFIG.style.chart.grid.y.axisLabels.color"
                                :x="drawingArea.left - 8"
                                :y="drawingArea.top + barSlot * i + barSlot / 2 + FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize / 3"
                                style="cursor: pointer"
                                @click="() => selectTimeLabel(timeLabel, i)"
                                v-html="createTSpansFromLineBreaksOnY({
                                    content: String(timeLabel.text),
                                    fontSize: FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize,
                                    fill: FINAL_CONFIG.style.chart.grid.y.axisLabels.color,
                                    x: drawingArea.left - 8,
                                    y: 0
                                })"
                            />
                        </g>
                    </g>
                </g>
            </template>

            <!-- TOOLTIP TRAPS (vertical mode) -->
            <template v-if="mutableConfig.showTooltip && FINAL_CONFIG.orientation === 'vertical'">            
                <rect
                    data-cy="tooltip-trap"
                    v-for="(_, i) in (slicer.end - slicer.start)"
                    :x="drawingArea.left + (i * barSlot)"
                    :y="drawingArea.top"
                    :width="barSlot"
                    :height="drawingArea.height < 0 ? 0 : drawingArea.height"
                    @click="() => selectDatapoint(i)"
                    @mouseenter="() => useTooltip(i)"
                    @mouseleave="() => onTrapLeave(i)"
                    :fill="i === trapIndex || i === selectedMinimapIndex ? FINAL_CONFIG.style.chart.highlighter.color : 'transparent'"
                    :style="{ opacity: FINAL_CONFIG.style.chart.highlighter.opacity / 100 }"
                />
            </template>

            <!-- TOOLTIP TRAPS (vertical mode) -->
            <template v-if="mutableConfig.showTooltip && FINAL_CONFIG.orientation === 'horizontal'">            
                <rect
                    data-cy="tooltip-trap"
                    v-for="(_, i) in (slicer.end - slicer.start)"
                    :x="drawingArea.left"
                    :y="drawingArea.top + (i * barSlot)"
                    :width="drawingArea.width < 0 ? 0 : drawingArea.width"
                    :height="barSlot"
                    @click="() => selectDatapoint(i)"
                    @mouseenter="() => useTooltip(i)"
                    @mouseleave="() => onTrapLeave(i)"
                    :fill="i === trapIndex || i === selectedMinimapIndex ? FINAL_CONFIG.style.chart.highlighter.color : 'transparent'"
                    :style="{ opacity: FINAL_CONFIG.style.chart.highlighter.opacity / 100 }"
                />
            </template>

            <!-- ZOOM PREVIEW -->
            <rect 
                v-if="isPrecog" 
                v-bind="precogRect" 
                :data-start="slicer.start" 
                :data-end="slicer.end"
            />

            <slot name="svg" :svg="{ 
                drawingArea,
                data: formattedDataset
            }"/>
        </svg>

        <div v-if="$slots.watermark" class="vue-data-ui-watermark">
            <slot name="watermark" v-bind="{ isPrinting: isPrinting || isImaging }"/>
        </div>


        <SlicerPreview 
            ref="chartSlicer"
            v-if="FINAL_CONFIG.style.chart.zoom.show && maxSeries > 6 && isDataset && slicerReady"
            :allMinimaps="allMinimaps"
            :background="FINAL_CONFIG.style.chart.zoom.color"
            :borderColor="FINAL_CONFIG.style.chart.backgroundColor"
            :customFormat="FINAL_CONFIG.style.chart.zoom.customFormat"
            :cutNullValues="false"
            :enableRangeHandles="FINAL_CONFIG.style.chart.zoom.enableRangeHandles"
            :enableSelectionDrag="FINAL_CONFIG.style.chart.zoom.enableSelectionDrag"
            :end="slicer.end"
            :focusOnDrag="FINAL_CONFIG.style.chart.zoom.focusOnDrag"
            :focusRangeRatio="FINAL_CONFIG.style.chart.zoom.focusRangeRatio"
            :fontSize="FINAL_CONFIG.style.chart.zoom.fontSize"
            :immediate="!FINAL_CONFIG.style.chart.zoom.preview.enable"
            :inputColor="FINAL_CONFIG.style.chart.zoom.color"
            :isPreview="isPrecog"
            :labelLeft="FINAL_CONFIG.style.chart.grid.x.timeLabels.values[slicer.start] ? timeLabels[0].text : ''"
            :labelRight="FINAL_CONFIG.style.chart.grid.x.timeLabels.values[slicer.end-1] ? timeLabels.at(-1).text : ''"
            :max="Math.max(...dataset.map(ds => ds.series.length))"
            :min="0"
            :minimap="datasetTotalsMinimap"
            :minimapCompact="FINAL_CONFIG.style.chart.zoom.minimap.compact"
            :minimapFrameColor="FINAL_CONFIG.style.chart.zoom.minimap.frameColor"
            :minimapIndicatorColor="FINAL_CONFIG.style.chart.zoom.minimap.indicatorColor"
            :minimapMerged="false"
            :minimapSelectedColor="FINAL_CONFIG.style.chart.zoom.minimap.selectedColor"
            :minimapSelectedColorOpacity="FINAL_CONFIG.style.chart.zoom.minimap.selectedColorOpacity"
            :minimapSelectedIndex="trapIndex"
            :minimapSelectionRadius="1"
            :preciseLabels="preciseAllTimeLabels.length ? preciseAllTimeLabels : allTimeLabels"
            :refreshEndPoint="FINAL_CONFIG.style.chart.zoom.endIndex !== null ? FINAL_CONFIG.style.chart.zoom.endIndex + 1 : Math.max(...dataset.map(ds => ds.series.length))"
            :refreshStartPoint="FINAL_CONFIG.style.chart.zoom.startIndex !== null ? FINAL_CONFIG.style.chart.zoom.startIndex : 0"
            :selectColor="FINAL_CONFIG.style.chart.zoom.highlightColor"
            :selectedSeries="datapointsForSlicerCustomFormat"
            :smoothMinimap="false"
            :start="slicer.start"
            :textColor="FINAL_CONFIG.style.chart.color"
            :timeLabels="allTimeLabels"
            :usePreciseLabels="FINAL_CONFIG.style.chart.grid.x.timeLabels.datetimeFormatter.enable && !FINAL_CONFIG.style.chart.zoom.useDefaultFormat"
            :valueEnd="slicer.end"
            :valueStart="slicer.start"
            :verticalHandles="FINAL_CONFIG.style.chart.zoom.minimap.verticalHandles"
            @update:end="onSlicerEnd"
            @update:start="onSlicerStart"
            @trapMouse="selectMinimapIndex"
            @reset="refreshSlicer"
            @futureEnd="v => setPrecog('end', v)"
            @futureStart="v => setPrecog('start', v)"
        >
            <template #reset-action="{ reset }">
                <slot name="reset-action" v-bind="{ reset }"/>
            </template>

            <template #slotMap="{ width: minimapW, height: minimapH, unitW }">
                <g v-for="dp in formattedDataset" :key="dp.id">
                    <template v-for="(x, j) in dp.xMinimap({ left: 0, unitW })" :key="j">
                    <rect
                        :x="j === 0
                            ? (x - (unitW * (FINAL_CONFIG.style.chart.bars.gapRatio / 4)))
                            : x - unitW / 2"
                        :y="Math.max(0, Math.min(minimapH, dp.yMinimap({ minimapH })[j]))"
                        :height="dp.heightMinimap({ minimapH })[j]"
                        :width="[0, maxSeries - 1].includes(j)
                            ? (unitW * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)) / 2
                            : unitW * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
                        :fill="FINAL_CONFIG.style.chart.bars.gradient.show ? `url(#gradient_${dp.id})` : dp.color"
                        :stroke="FINAL_CONFIG.style.chart.backgroundColor"
                        :stroke-width="0.5"
                        rx="0"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        :style="{
                            opacity: j >= slicerPrecog.start && j <= slicerPrecog.end ? 1 : 0.62
                        }"
                    />
                    </template>
                </g>
            </template>

        </SlicerPreview>

        <Tooltip
            :show="mutableConfig.showTooltip && isTooltip"
            :backgroundColor="FINAL_CONFIG.style.chart.tooltip.backgroundColor"
            :color="FINAL_CONFIG.style.chart.tooltip.color"
            :fontSize="FINAL_CONFIG.style.chart.tooltip.fontSize"
            :borderRadius="FINAL_CONFIG.style.chart.tooltip.borderRadius"
            :borderColor="FINAL_CONFIG.style.chart.tooltip.borderColor"
            :borderWidth="FINAL_CONFIG.style.chart.tooltip.borderWidth"
            :backgroundOpacity="FINAL_CONFIG.style.chart.tooltip.backgroundOpacity"
            :position="FINAL_CONFIG.style.chart.tooltip.position"
            :offsetY="FINAL_CONFIG.style.chart.tooltip.offsetY"
            :parent="stackbarChart"
            :content="tooltipContent"
            :isFullscreen="isFullscreen"
            :isCustom="isFunction(FINAL_CONFIG.style.chart.tooltip.customFormat)"
            :smooth="FINAL_CONFIG.style.chart.tooltip.smooth"
            :backdropFilter="FINAL_CONFIG.style.chart.tooltip.backdropFilter"
            :smoothForce="FINAL_CONFIG.style.chart.tooltip.smoothForce"
            :smoothSnapThreshold="FINAL_CONFIG.style.chart.tooltip.smoothSnapThreshold"
        >
            <template #tooltip-before>
                <slot name="tooltip-before" v-bind="{...dataTooltipSlot}"></slot>
            </template>
            <template #tooltip-after>
                <slot name="tooltip-after" v-bind="{...dataTooltipSlot}"></slot>
            </template>
        </Tooltip>

        <div :id="`legend-bottom-${uid}`" />

        <!-- LEGEND -->
        <Teleport v-if="readyTeleport" :to="FINAL_CONFIG.style.chart.legend.position === 'top' ? `#legend-top-${uid}` : `#legend-bottom-${uid}`">
            <div ref="chartLegend">
                <Legend v-if="FINAL_CONFIG.style.chart.legend.show" :legendSet="legendSet" :config="legendConfig"
                    @clickMarker="({ legend }) => legend.segregate()">
                    <template #legend-pattern="{ legend, index }" v-if="$slots.pattern">
                        <Shape
                            :shape="legend.shape"
                            :radius="30"
                            stroke="none"
                            :plot="{ x: 30, y: 30}"
                            :fill="`url(#pattern_${uid}_${index})`"
                        />
                    </template>
    
                    <template #item="{ legend }">
                        <div @click="legend.segregate()" :style="`opacity:${segregated.includes(legend.id) ? 0.5 : 1}`" v-if="!loading">
                            {{ legend.name }}
                        </div>
                    </template>
                </Legend>
        
                <slot v-else name="legend" v-bind:legend="legendSet" />
            </div>
        </Teleport>

        <div v-if="$slots.source" ref="source" dir="auto">
            <slot name="source" />
        </div>

        <component
            v-if="isDataset && FINAL_CONFIG.userOptions.buttons.table"
            :is="tableComponent.component"
            v-bind="tableComponent.props"
            ref="tableUnit"
            @close="closeTable"
        >
            <template #title v-if="FINAL_CONFIG.table.useDialog">
                {{ tableComponent.title }}
            </template>
            <template #actions v-if="FINAL_CONFIG.table.useDialog">
                <button tabindex="0" class="vue-ui-user-options-button" @click="generateCsv(FINAL_CONFIG.userOptions.callbacks.csv)">
                    <BaseIcon name="excel" :stroke="tableComponent.props.color"/>
                </button>
            </template>
            <template #content>
                <DataTable 
                    :colNames="dataTable.colNames"
                    :head="dataTable.head"
                    :body="dataTable.body"
                    :config="dataTable.config"
                    :title="FINAL_CONFIG.table.useDialog ? '' : tableComponent.title"
                    :withCloseButton="!FINAL_CONFIG.table.useDialog"
                    @close="closeTable"
                >
                    <template #th="{ th }">
                        <div v-html="th"/>
                    </template>
                    <template #td="{ td }">
                        {{ !isNaN(Number(td)) ? dataLabel({
                            p: FINAL_CONFIG.style.chart.bars.dataLabels.prefix,
                            v: td,
                            s: FINAL_CONFIG.style.chart.bars.dataLabels.suffix,
                            r: FINAL_CONFIG.table.td.roundingValue,
                        }) : td }}
                    </template>
                </DataTable>
            </template>
        </component>

        <!-- v3 Skeleton loader -->
        <BaseScanner v-if="loading" />
    </div>
</template>

<style scoped lang="scss">
@import "../vue-data-ui.css";
.vue-ui-stackbar * {
    transition: unset;
}

.vue-ui-stackbar {
    user-select: none;
    width: 100%;
}

.vue-data-ui-bar-animated {
    animation: vueDataUiBarAnimation 0.5s ease-in-out;
    transform-origin: center;
}

.vue-data-ui-bar-transition {
    transition: all 0.2s ease-in-out !important;
}

@keyframes vueDataUiBarAnimation {
    0% {
        transform: scale(0.9,0.9);
        opacity: 0;
    }
    80% {
        transform: scale(1.02,1.02);
        opacity: 1;
    }
    to {
        transform: scale(1,1);
        opacity: 1;
    }
}

.vue-data-ui-loading {
    rect, line, text {
        animation: none !important;
        transition: none !important;
    }
}
</style>